mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-21 12:12:34 +00:00
Compare commits
29 Commits
ripple/con
...
copilot/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00d280c965 | ||
|
|
2b14ee3018 | ||
|
|
ce31a7ed16 | ||
|
|
91a23cf80b | ||
|
|
e460ea0840 | ||
|
|
46d5c67a8d | ||
|
|
ce9ccf844a | ||
|
|
c791cae1ec | ||
|
|
7b3724b7a3 | ||
|
|
bee2d112c6 | ||
|
|
01c977bbfe | ||
|
|
3baf5454f2 | ||
|
|
24a5cbaa93 | ||
|
|
eb7c8c6c7a | ||
|
|
f27d8f3890 | ||
|
|
8345cd77df | ||
|
|
c38aabdaee | ||
|
|
a896ed3987 | ||
|
|
1a7d67c4db | ||
|
|
92983d8040 | ||
|
|
320a65f77c | ||
|
|
45b8c4d732 | ||
|
|
e284969ae4 | ||
|
|
ad116a35ff | ||
|
|
249fb12e8f | ||
|
|
cbabee1bec | ||
|
|
cf2835e3c1 | ||
|
|
9b0e87a37e | ||
|
|
4a31ee1926 |
17
.clang-tidy
17
.clang-tidy
@@ -14,6 +14,7 @@ Checks: "-*,
|
||||
bugprone-fold-init-type,
|
||||
bugprone-forward-declaration-namespace,
|
||||
bugprone-inaccurate-erase,
|
||||
bugprone-inc-dec-in-conditions,
|
||||
bugprone-incorrect-enable-if,
|
||||
bugprone-incorrect-roundings,
|
||||
bugprone-infinite-loop,
|
||||
@@ -30,9 +31,12 @@ Checks: "-*,
|
||||
bugprone-multiple-statement-macro,
|
||||
bugprone-no-escape,
|
||||
bugprone-non-zero-enum-to-bool-conversion,
|
||||
bugprone-optional-value-conversion,
|
||||
bugprone-parent-virtual-call,
|
||||
bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
bugprone-posix-return,
|
||||
bugprone-redundant-branch-condition,
|
||||
bugprone-reserved-identifier,
|
||||
bugprone-return-const-ref-from-parameter,
|
||||
bugprone-shared-ptr-array-mismatch,
|
||||
bugprone-signal-handler,
|
||||
@@ -53,14 +57,18 @@ Checks: "-*,
|
||||
bugprone-suspicious-realloc-usage,
|
||||
bugprone-suspicious-semicolon,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-suspicious-stringview-data-usage,
|
||||
bugprone-swapped-arguments,
|
||||
bugprone-terminating-continue,
|
||||
bugprone-throw-keyword-missing,
|
||||
bugprone-too-small-loop-variable,
|
||||
bugprone-undefined-memory-manipulation,
|
||||
bugprone-undelegated-constructor,
|
||||
bugprone-unhandled-exception-at-new,
|
||||
bugprone-unhandled-self-assignment,
|
||||
bugprone-unique-ptr-array-mismatch,
|
||||
bugprone-unsafe-functions,
|
||||
bugprone-unused-raii,
|
||||
bugprone-unused-local-non-trivial-variable,
|
||||
bugprone-virtual-near-miss,
|
||||
cppcoreguidelines-no-suspend-with-lock,
|
||||
@@ -90,18 +98,11 @@ Checks: "-*,
|
||||
# checks that have some issues that need to be resolved:
|
||||
#
|
||||
# bugprone-crtp-constructor-accessibility,
|
||||
# bugprone-inc-dec-in-conditions,
|
||||
# bugprone-reserved-identifier,
|
||||
# bugprone-move-forwarding-reference,
|
||||
# bugprone-switch-missing-default-case,
|
||||
# bugprone-suspicious-stringview-data-usage,
|
||||
# bugprone-pointer-arithmetic-on-polymorphic-object,
|
||||
# bugprone-optional-value-conversion,
|
||||
# bugprone-too-small-loop-variable,
|
||||
# bugprone-unused-raii,
|
||||
# bugprone-unused-return-value,
|
||||
# bugprone-use-after-move,
|
||||
# bugprone-unhandled-self-assignment,
|
||||
# bugprone-unused-raii,
|
||||
#
|
||||
# cppcoreguidelines-misleading-capture-default-by-value,
|
||||
# cppcoreguidelines-init-variables,
|
||||
|
||||
16
.github/pull_request_template.md
vendored
16
.github/pull_request_template.md
vendored
@@ -29,22 +29,6 @@ If a refactor, how is this better than the previous implementation?
|
||||
If there is a spec or design document for this feature, please link it here.
|
||||
-->
|
||||
|
||||
### Type of Change
|
||||
|
||||
<!--
|
||||
Please check [x] relevant options, delete irrelevant ones.
|
||||
-->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Refactor (non-breaking change that only restructures code)
|
||||
- [ ] Performance (increase or change in throughput and/or latency)
|
||||
- [ ] Tests (you added tests for code that already exists, or your new feature included in this PR)
|
||||
- [ ] Documentation update
|
||||
- [ ] Chore (no impact to binary, e.g. `.gitignore`, formatting, dropping support for older tooling)
|
||||
- [ ] Release
|
||||
|
||||
### API Impact
|
||||
|
||||
<!--
|
||||
|
||||
6
.github/scripts/levelization/README.md
vendored
6
.github/scripts/levelization/README.md
vendored
@@ -70,7 +70,7 @@ that `test` code should _never_ be included in `xrpl` or `xrpld` code.)
|
||||
|
||||
## Validation
|
||||
|
||||
The [levelization](generate.sh) script takes no parameters,
|
||||
The [levelization](generate.py) script takes no parameters,
|
||||
reads no environment variables, and can be run from any directory,
|
||||
as long as it is in the expected location in the rippled repo.
|
||||
It can be run at any time from within a checked out repo, and will
|
||||
@@ -104,7 +104,7 @@ It generates many files of [results](results):
|
||||
Github Actions workflow to test that levelization loops haven't
|
||||
changed. Unfortunately, if changes are detected, it can't tell if
|
||||
they are improvements or not, so if you have resolved any issues or
|
||||
done anything else to improve levelization, run `levelization.sh`,
|
||||
done anything else to improve levelization, run `generate.py`,
|
||||
and commit the updated results.
|
||||
|
||||
The `loops.txt` and `ordering.txt` files relate the modules
|
||||
@@ -128,7 +128,7 @@ The committed files hide the detailed values intentionally, to
|
||||
prevent false alarms and merging issues, and because it's easy to
|
||||
get those details locally.
|
||||
|
||||
1. Run `levelization.sh`
|
||||
1. Run `generate.py`
|
||||
2. Grep the modules in `paths.txt`.
|
||||
- For example, if a cycle is found `A ~= B`, simply `grep -w
|
||||
A .github/scripts/levelization/results/paths.txt | grep -w B`
|
||||
|
||||
335
.github/scripts/levelization/generate.py
vendored
Normal file
335
.github/scripts/levelization/generate.py
vendored
Normal file
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Usage: generate.py
|
||||
This script takes no parameters, and can be called from any directory in the file system.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Set, Optional
|
||||
|
||||
# Compile regex patterns once at module level
|
||||
INCLUDE_PATTERN = re.compile(r"^\s*#include.*/.*\.h")
|
||||
INCLUDE_PATH_PATTERN = re.compile(r'[<"]([^>"]+)[>"]')
|
||||
|
||||
|
||||
def dictionary_sort_key(s: str) -> str:
|
||||
"""
|
||||
Create a sort key that mimics 'sort -d' (dictionary order).
|
||||
Dictionary order only considers blanks and alphanumeric characters.
|
||||
This means punctuation like '.' is ignored during sorting.
|
||||
"""
|
||||
# Keep only alphanumeric characters and spaces
|
||||
return "".join(c for c in s if c.isalnum() or c.isspace())
|
||||
|
||||
|
||||
def get_level(file_path: str) -> str:
|
||||
"""
|
||||
Extract the level from a file path (second and third directory components).
|
||||
Equivalent to bash: cut -d/ -f 2,3
|
||||
|
||||
Examples:
|
||||
src/xrpld/app/main.cpp -> xrpld.app
|
||||
src/libxrpl/protocol/STObject.cpp -> libxrpl.protocol
|
||||
include/xrpl/basics/base_uint.h -> xrpl.basics
|
||||
"""
|
||||
parts = file_path.split("/")
|
||||
|
||||
# Get fields 2 and 3 (indices 1 and 2 in 0-based indexing)
|
||||
if len(parts) >= 3:
|
||||
level = f"{parts[1]}/{parts[2]}"
|
||||
elif len(parts) >= 2:
|
||||
level = f"{parts[1]}/toplevel"
|
||||
else:
|
||||
level = file_path
|
||||
|
||||
# If the "level" indicates a file, cut off the filename
|
||||
if "." in level.split("/")[-1]: # Avoid Path object creation
|
||||
# Use the "toplevel" label as a workaround for `sort`
|
||||
# inconsistencies between different utility versions
|
||||
level = level.rsplit("/", 1)[0] + "/toplevel"
|
||||
|
||||
return level.replace("/", ".")
|
||||
|
||||
|
||||
def extract_include_level(include_line: str) -> Optional[str]:
|
||||
"""
|
||||
Extract the include path from an #include directive.
|
||||
Gets the first two directory components from the include path.
|
||||
Equivalent to bash: cut -d/ -f 1,2
|
||||
|
||||
Examples:
|
||||
#include <xrpl/basics/base_uint.h> -> xrpl.basics
|
||||
#include "xrpld/app/main/Application.h" -> xrpld.app
|
||||
"""
|
||||
# Remove everything before the quote or angle bracket
|
||||
match = INCLUDE_PATH_PATTERN.search(include_line)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
include_path = match.group(1)
|
||||
parts = include_path.split("/")
|
||||
|
||||
# Get first two fields (indices 0 and 1)
|
||||
if len(parts) >= 2:
|
||||
include_level = f"{parts[0]}/{parts[1]}"
|
||||
else:
|
||||
include_level = include_path
|
||||
|
||||
# If the "includelevel" indicates a file, cut off the filename
|
||||
if "." in include_level.split("/")[-1]: # Avoid Path object creation
|
||||
include_level = include_level.rsplit("/", 1)[0] + "/toplevel"
|
||||
|
||||
return include_level.replace("/", ".")
|
||||
|
||||
|
||||
def find_repository_directories(
|
||||
start_path: Path, depth_limit: int = 10
|
||||
) -> Tuple[Path, List[Path]]:
|
||||
"""
|
||||
Find the repository root by looking for src or include folders.
|
||||
Walks up the directory tree from the start path.
|
||||
"""
|
||||
current = start_path.resolve()
|
||||
|
||||
# Walk up the directory tree
|
||||
for _ in range(depth_limit): # Limit search depth to prevent infinite loops
|
||||
src_path = current / "src"
|
||||
include_path = current / "include"
|
||||
# Check if this directory has src or include folders
|
||||
has_src = src_path.exists()
|
||||
has_include = include_path.exists()
|
||||
|
||||
if has_src or has_include:
|
||||
return current, [src_path, include_path]
|
||||
|
||||
# Move up one level
|
||||
parent = current.parent
|
||||
if parent == current: # Reached filesystem root
|
||||
break
|
||||
current = parent
|
||||
|
||||
# If we couldn't find it, raise an error
|
||||
raise RuntimeError(
|
||||
"Could not find repository root. "
|
||||
"Expected to find a directory containing 'src' and/or 'include' folders."
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
# Change to the script's directory
|
||||
script_dir = Path(__file__).parent.resolve()
|
||||
os.chdir(script_dir)
|
||||
|
||||
# Clean up and create results directory.
|
||||
results_dir = script_dir / "results"
|
||||
if results_dir.exists():
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(results_dir)
|
||||
results_dir.mkdir()
|
||||
|
||||
# Find the repository root by searching for src and include directories.
|
||||
try:
|
||||
repo_root, scan_dirs = find_repository_directories(script_dir)
|
||||
|
||||
print(f"Found repository root: {repo_root}")
|
||||
print(f"Scanning directories:")
|
||||
for scan_dir in scan_dirs:
|
||||
print(f" - {scan_dir.relative_to(repo_root)}")
|
||||
except RuntimeError as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print("\nScanning for raw includes...")
|
||||
# Find all #include directives
|
||||
raw_includes: List[Tuple[str, str]] = []
|
||||
rawincludes_file = results_dir / "rawincludes.txt"
|
||||
|
||||
# Write to file as we go to avoid storing everything in memory.
|
||||
with open(rawincludes_file, "w", buffering=8192) as raw_f:
|
||||
for dir_path in scan_dirs:
|
||||
print(f" Scanning {dir_path.relative_to(repo_root)}...")
|
||||
|
||||
for file_path in dir_path.rglob("*"):
|
||||
if not file_path.is_file():
|
||||
continue
|
||||
|
||||
try:
|
||||
rel_path_str = str(file_path.relative_to(repo_root))
|
||||
|
||||
# Read file with a large buffer for performance.
|
||||
with open(
|
||||
file_path,
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
errors="ignore",
|
||||
buffering=8192,
|
||||
) as f:
|
||||
for line in f:
|
||||
# Quick check before regex
|
||||
if "#include" not in line or "boost" in line:
|
||||
continue
|
||||
|
||||
if INCLUDE_PATTERN.match(line):
|
||||
line_stripped = line.strip()
|
||||
entry = f"{rel_path_str}:{line_stripped}\n"
|
||||
print(entry, end="")
|
||||
raw_f.write(entry)
|
||||
raw_includes.append((rel_path_str, line_stripped))
|
||||
except Exception as e:
|
||||
print(f"Error reading {file_path}: {e}", file=sys.stderr)
|
||||
|
||||
# Build levelization paths and count directly (no need to sort first).
|
||||
print("Build levelization paths")
|
||||
path_counts: Dict[Tuple[str, str], int] = defaultdict(int)
|
||||
|
||||
for file_path, include_line in raw_includes:
|
||||
include_level = extract_include_level(include_line)
|
||||
if not include_level:
|
||||
continue
|
||||
|
||||
level = get_level(file_path)
|
||||
if level != include_level:
|
||||
path_counts[(level, include_level)] += 1
|
||||
|
||||
# Sort and deduplicate paths (using dictionary order like bash 'sort -d').
|
||||
print("Sort and deduplicate paths")
|
||||
|
||||
paths_file = results_dir / "paths.txt"
|
||||
with open(paths_file, "w") as f:
|
||||
# Sort using dictionary order: only alphanumeric and spaces matter
|
||||
sorted_items = sorted(
|
||||
path_counts.items(),
|
||||
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
|
||||
)
|
||||
for (level, include_level), count in sorted_items:
|
||||
line = f"{count:7} {level} {include_level}\n"
|
||||
print(line.rstrip())
|
||||
f.write(line)
|
||||
|
||||
# Split into flat-file database
|
||||
print("Split into flat-file database")
|
||||
includes_dir = results_dir / "includes"
|
||||
included_by_dir = results_dir / "included_by"
|
||||
includes_dir.mkdir()
|
||||
included_by_dir.mkdir()
|
||||
|
||||
# Batch writes by grouping data first to avoid repeated file opens.
|
||||
includes_data: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
|
||||
included_by_data: Dict[str, List[Tuple[str, int]]] = defaultdict(list)
|
||||
|
||||
# Process in sorted order to match bash script behaviour (dictionary order).
|
||||
sorted_items = sorted(
|
||||
path_counts.items(),
|
||||
key=lambda x: (dictionary_sort_key(x[0][0]), dictionary_sort_key(x[0][1])),
|
||||
)
|
||||
for (level, include_level), count in sorted_items:
|
||||
includes_data[level].append((include_level, count))
|
||||
included_by_data[include_level].append((level, count))
|
||||
|
||||
# Write all includes files in sorted order (dictionary order).
|
||||
for level in sorted(includes_data.keys(), key=dictionary_sort_key):
|
||||
entries = includes_data[level]
|
||||
with open(includes_dir / level, "w") as f:
|
||||
for include_level, count in entries:
|
||||
line = f"{include_level} {count}\n"
|
||||
print(line.rstrip())
|
||||
f.write(line)
|
||||
|
||||
# Write all included_by files in sorted order (dictionary order).
|
||||
for include_level in sorted(included_by_data.keys(), key=dictionary_sort_key):
|
||||
entries = included_by_data[include_level]
|
||||
with open(included_by_dir / include_level, "w") as f:
|
||||
for level, count in entries:
|
||||
line = f"{level} {count}\n"
|
||||
print(line.rstrip())
|
||||
f.write(line)
|
||||
|
||||
# Search for loops
|
||||
print("Search for loops")
|
||||
loops_file = results_dir / "loops.txt"
|
||||
ordering_file = results_dir / "ordering.txt"
|
||||
|
||||
loops_found: Set[Tuple[str, str]] = set()
|
||||
|
||||
# Pre-load all include files into memory to avoid repeated I/O.
|
||||
# This is the biggest optimisation - we were reading files repeatedly in nested loops.
|
||||
# Use list of tuples to preserve file order.
|
||||
includes_cache: Dict[str, List[Tuple[str, int]]] = {}
|
||||
includes_lookup: Dict[str, Dict[str, int]] = {} # For fast lookup
|
||||
|
||||
# Note: bash script uses 'for source in *' which uses standard glob sorting,
|
||||
# NOT dictionary order. So we use standard sorted() here, not dictionary_sort_key.
|
||||
for include_file in sorted(includes_dir.iterdir(), key=lambda p: p.name):
|
||||
if not include_file.is_file():
|
||||
continue
|
||||
|
||||
includes_cache[include_file.name] = []
|
||||
includes_lookup[include_file.name] = {}
|
||||
with open(include_file, "r") as f:
|
||||
for line in f:
|
||||
parts = line.strip().split()
|
||||
if len(parts) >= 2:
|
||||
include_name = parts[0]
|
||||
include_count = int(parts[1])
|
||||
includes_cache[include_file.name].append(
|
||||
(include_name, include_count)
|
||||
)
|
||||
includes_lookup[include_file.name][include_name] = include_count
|
||||
|
||||
with open(loops_file, "w", buffering=8192) as loops_f, open(
|
||||
ordering_file, "w", buffering=8192
|
||||
) as ordering_f:
|
||||
|
||||
# Use standard sorting to match bash glob expansion 'for source in *'.
|
||||
for source in sorted(includes_cache.keys()):
|
||||
source_includes = includes_cache[source]
|
||||
|
||||
for include, include_freq in source_includes:
|
||||
# Check if include file exists and references source
|
||||
if include not in includes_lookup:
|
||||
continue
|
||||
|
||||
source_freq = includes_lookup[include].get(source)
|
||||
|
||||
if source_freq is not None:
|
||||
# Found a loop
|
||||
loop_key = tuple(sorted([source, include]))
|
||||
if loop_key in loops_found:
|
||||
continue
|
||||
loops_found.add(loop_key)
|
||||
|
||||
loops_f.write(f"Loop: {source} {include}\n")
|
||||
|
||||
# If the counts are close, indicate that the two modules are
|
||||
# on the same level, though they shouldn't be.
|
||||
diff = include_freq - source_freq
|
||||
if diff > 3:
|
||||
loops_f.write(f" {source} > {include}\n\n")
|
||||
elif diff < -3:
|
||||
loops_f.write(f" {include} > {source}\n\n")
|
||||
elif source_freq == include_freq:
|
||||
loops_f.write(f" {include} == {source}\n\n")
|
||||
else:
|
||||
loops_f.write(f" {include} ~= {source}\n\n")
|
||||
else:
|
||||
ordering_f.write(f"{source} > {include}\n")
|
||||
|
||||
# Print results
|
||||
print("\nOrdering:")
|
||||
with open(ordering_file, "r") as f:
|
||||
print(f.read(), end="")
|
||||
|
||||
print("\nLoops:")
|
||||
with open(loops_file, "r") as f:
|
||||
print(f.read(), end="")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
130
.github/scripts/levelization/generate.sh
vendored
130
.github/scripts/levelization/generate.sh
vendored
@@ -1,130 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Usage: generate.sh
|
||||
# This script takes no parameters, reads no environment variables,
|
||||
# and can be run from any directory, as long as it is in the expected
|
||||
# location in the repo.
|
||||
|
||||
pushd $( dirname $0 )
|
||||
|
||||
if [ -v PS1 ]
|
||||
then
|
||||
# if the shell is interactive, clean up any flotsam before analyzing
|
||||
git clean -ix
|
||||
fi
|
||||
|
||||
# Ensure all sorting is ASCII-order consistently across platforms.
|
||||
export LANG=C
|
||||
|
||||
rm -rfv results
|
||||
mkdir results
|
||||
includes="$( pwd )/results/rawincludes.txt"
|
||||
pushd ../../..
|
||||
echo Raw includes:
|
||||
grep -r '^[ ]*#include.*/.*\.h' include src | \
|
||||
grep -v boost | tee ${includes}
|
||||
popd
|
||||
pushd results
|
||||
|
||||
oldifs=${IFS}
|
||||
IFS=:
|
||||
mkdir includes
|
||||
mkdir included_by
|
||||
echo Build levelization paths
|
||||
exec 3< ${includes} # open rawincludes.txt for input
|
||||
while read -r -u 3 file include
|
||||
do
|
||||
level=$( echo ${file} | cut -d/ -f 2,3 )
|
||||
# If the "level" indicates a file, cut off the filename
|
||||
if [[ "${level##*.}" != "${level}" ]]
|
||||
then
|
||||
# Use the "toplevel" label as a workaround for `sort`
|
||||
# inconsistencies between different utility versions
|
||||
level="$( dirname ${level} )/toplevel"
|
||||
fi
|
||||
level=$( echo ${level} | tr '/' '.' )
|
||||
|
||||
includelevel=$( echo ${include} | sed 's/.*["<]//; s/[">].*//' | \
|
||||
cut -d/ -f 1,2 )
|
||||
if [[ "${includelevel##*.}" != "${includelevel}" ]]
|
||||
then
|
||||
# Use the "toplevel" label as a workaround for `sort`
|
||||
# inconsistencies between different utility versions
|
||||
includelevel="$( dirname ${includelevel} )/toplevel"
|
||||
fi
|
||||
includelevel=$( echo ${includelevel} | tr '/' '.' )
|
||||
|
||||
if [[ "$level" != "$includelevel" ]]
|
||||
then
|
||||
echo $level $includelevel | tee -a paths.txt
|
||||
fi
|
||||
done
|
||||
echo Sort and deduplicate paths
|
||||
sort -ds paths.txt | uniq -c | tee sortedpaths.txt
|
||||
mv sortedpaths.txt paths.txt
|
||||
exec 3>&- #close fd 3
|
||||
IFS=${oldifs}
|
||||
unset oldifs
|
||||
|
||||
echo Split into flat-file database
|
||||
exec 4<paths.txt # open paths.txt for input
|
||||
while read -r -u 4 count level include
|
||||
do
|
||||
echo ${include} ${count} | tee -a includes/${level}
|
||||
echo ${level} ${count} | tee -a included_by/${include}
|
||||
done
|
||||
exec 4>&- #close fd 4
|
||||
|
||||
loops="$( pwd )/loops.txt"
|
||||
ordering="$( pwd )/ordering.txt"
|
||||
pushd includes
|
||||
echo Search for loops
|
||||
# Redirect stdout to a file
|
||||
exec 4>&1
|
||||
exec 1>"${loops}"
|
||||
for source in *
|
||||
do
|
||||
if [[ -f "$source" ]]
|
||||
then
|
||||
exec 5<"${source}" # open for input
|
||||
while read -r -u 5 include includefreq
|
||||
do
|
||||
if [[ -f $include ]]
|
||||
then
|
||||
if grep -q -w $source $include
|
||||
then
|
||||
if grep -q -w "Loop: $include $source" "${loops}"
|
||||
then
|
||||
continue
|
||||
fi
|
||||
sourcefreq=$( grep -w $source $include | cut -d\ -f2 )
|
||||
echo "Loop: $source $include"
|
||||
# If the counts are close, indicate that the two modules are
|
||||
# on the same level, though they shouldn't be
|
||||
if [[ $(( $includefreq - $sourcefreq )) -gt 3 ]]
|
||||
then
|
||||
echo -e " $source > $include\n"
|
||||
elif [[ $(( $sourcefreq - $includefreq )) -gt 3 ]]
|
||||
then
|
||||
echo -e " $include > $source\n"
|
||||
elif [[ $sourcefreq -eq $includefreq ]]
|
||||
then
|
||||
echo -e " $include == $source\n"
|
||||
else
|
||||
echo -e " $include ~= $source\n"
|
||||
fi
|
||||
else
|
||||
echo "$source > $include" >> "${ordering}"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
exec 5>&- #close fd 5
|
||||
fi
|
||||
done
|
||||
exec 1>&4 #close fd 1
|
||||
exec 4>&- #close fd 4
|
||||
cat "${ordering}"
|
||||
cat "${loops}"
|
||||
popd
|
||||
popd
|
||||
popd
|
||||
4
.github/scripts/strategy-matrix/generate.py
vendored
4
.github/scripts/strategy-matrix/generate.py
vendored
@@ -55,7 +55,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
# fee to 500.
|
||||
# - Bookworm using GCC 15: Debug on linux/amd64, enable code
|
||||
# coverage (which will be done below).
|
||||
# - Bookworm using Clang 16: Debug on linux/arm64, enable voidstar.
|
||||
# - Bookworm using Clang 16: Debug on linux/amd64, enable voidstar.
|
||||
# - Bookworm using Clang 17: Release on linux/amd64, set the
|
||||
# reference fee to 1000.
|
||||
# - Bookworm using Clang 20: Debug on linux/amd64.
|
||||
@@ -78,7 +78,7 @@ def generate_strategy_matrix(all: bool, config: Config) -> list:
|
||||
if (
|
||||
f"{os['compiler_name']}-{os['compiler_version']}" == "clang-16"
|
||||
and build_type == "Debug"
|
||||
and architecture["platform"] == "linux/arm64"
|
||||
and architecture["platform"] == "linux/amd64"
|
||||
):
|
||||
cmake_args = f"-Dvoidstar=ON {cmake_args}"
|
||||
skip = False
|
||||
|
||||
5
.github/workflows/on-pr.yml
vendored
5
.github/workflows/on-pr.yml
vendored
@@ -141,9 +141,8 @@ jobs:
|
||||
needs:
|
||||
- should-run
|
||||
- build-test
|
||||
# Only run when committing to a PR that targets a release branch in the
|
||||
# XRPLF repository.
|
||||
if: ${{ github.repository_owner == 'XRPLF' && needs.should-run.outputs.go == 'true' && startsWith(github.ref, 'refs/heads/release') }}
|
||||
# Only run when committing to a PR that targets a release branch.
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && needs.should-run.outputs.go == 'true' && github.event_name == 'pull_request' && startsWith(github.event.pull_request.base.ref, 'release') }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
|
||||
3
.github/workflows/on-tag.yml
vendored
3
.github/workflows/on-tag.yml
vendored
@@ -17,8 +17,7 @@ defaults:
|
||||
|
||||
jobs:
|
||||
upload-recipe:
|
||||
# Only run when a tag is pushed to the XRPLF repository.
|
||||
if: ${{ github.repository_owner == 'XRPLF' }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
|
||||
4
.github/workflows/on-trigger.yml
vendored
4
.github/workflows/on-trigger.yml
vendored
@@ -92,8 +92,8 @@ jobs:
|
||||
|
||||
upload-recipe:
|
||||
needs: build-test
|
||||
# Only run when pushing to the develop branch in the XRPLF repository.
|
||||
if: ${{ github.repository_owner == 'XRPLF' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
# Only run when pushing to the develop branch.
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' && github.ref == 'refs/heads/develop' }}
|
||||
uses: ./.github/workflows/reusable-upload-recipe.yml
|
||||
secrets:
|
||||
remote_username: ${{ secrets.CONAN_REMOTE_USERNAME }}
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@56de1bdf19639e009639a50b8d17c28ca954f267
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@44856eb0d6ecb7d376370244324ab3dc8b863bad
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
@@ -176,7 +176,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload the binary (Linux)
|
||||
if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && runner.os == 'Linux' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: xrpld-${{ inputs.config_name }}
|
||||
@@ -230,6 +230,8 @@ jobs:
|
||||
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
|
||||
run: |
|
||||
set -o pipefail
|
||||
# Coverage builds are slower due to instrumentation; use fewer parallel jobs to avoid flakiness
|
||||
[ "$COVERAGE_ENABLED" = "true" ] && BUILD_NPROC=$(( BUILD_NPROC - 2 ))
|
||||
./xrpld --unittest --unittest-jobs "${BUILD_NPROC}" 2>&1 | tee unittest.log
|
||||
|
||||
- name: Show test failure summary
|
||||
@@ -266,7 +268,7 @@ jobs:
|
||||
--target coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository_owner == 'XRPLF' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
disable_search: true
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Check levelization
|
||||
run: .github/scripts/levelization/generate.sh
|
||||
run: python .github/scripts/levelization/generate.py
|
||||
- name: Check for differences
|
||||
env:
|
||||
MESSAGE: |
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
removed from loops.txt, it's probably an improvement, while if
|
||||
something was added, it's probably a regression.
|
||||
|
||||
Run '.github/scripts/levelization/generate.sh' in your repo, commit
|
||||
Run '.github/scripts/levelization/generate.py' in your repo, commit
|
||||
and push the changes. See .github/scripts/levelization/README.md for
|
||||
more info.
|
||||
run: |
|
||||
|
||||
2
.github/workflows/reusable-clang-tidy.yml
vendored
2
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -51,5 +51,5 @@ jobs:
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.any_cpp_changed == 'true' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
uses: ./.github/workflows/reusable-clang-tidy-files.yml
|
||||
with:
|
||||
files: ${{ (needs.determine-files.outputs.clang_tidy_config_changed == 'true' && '') || (inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '') }}
|
||||
files: ${{ needs.determine-files.outputs.clang_tidy_config_changed == 'true' && '' || (inputs.check_only_changed && needs.determine-files.outputs.all_changed_files || '') }}
|
||||
create_issue_on_failure: ${{ inputs.create_issue_on_failure }}
|
||||
|
||||
10
.github/workflows/reusable-upload-recipe.yml
vendored
10
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -69,22 +69,28 @@ jobs:
|
||||
conan export . --version=${{ steps.version.outputs.version }}
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/${{ steps.version.outputs.version }}
|
||||
|
||||
# When this workflow is triggered by a push event, it will always be when merging into the
|
||||
# 'develop' branch, see on-trigger.yml.
|
||||
- name: Upload Conan recipe (develop)
|
||||
if: ${{ github.ref == 'refs/heads/develop' }}
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=develop
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/develop
|
||||
|
||||
# When this workflow is triggered by a pull request event, it will always be when merging into
|
||||
# one of the 'release' branches, see on-pr.yml.
|
||||
- name: Upload Conan recipe (rc)
|
||||
if: ${{ startsWith(github.ref, 'refs/heads/release') }}
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
REMOTE_NAME: ${{ inputs.remote_name }}
|
||||
run: |
|
||||
conan export . --version=rc
|
||||
conan upload --confirm --check --remote="${REMOTE_NAME}" xrpl/rc
|
||||
|
||||
# When this workflow is triggered by a tag event, it will always be when tagging a final
|
||||
# release, see on-tag.yml.
|
||||
- name: Upload Conan recipe (release)
|
||||
if: ${{ github.event_name == 'tag' }}
|
||||
env:
|
||||
|
||||
4
.github/workflows/upload-conan-deps.yml
vendored
4
.github/workflows/upload-conan-deps.yml
vendored
@@ -103,11 +103,11 @@ jobs:
|
||||
sanitizers: ${{ matrix.sanitizers }}
|
||||
|
||||
- name: Log into Conan remote
|
||||
if: ${{ github.repository_owner == 'XRPLF' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
run: conan remote login "${CONAN_REMOTE_NAME}" "${{ secrets.CONAN_REMOTE_USERNAME }}" --password "${{ secrets.CONAN_REMOTE_PASSWORD }}"
|
||||
|
||||
- name: Upload Conan packages
|
||||
if: ${{ github.repository_owner == 'XRPLF' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') }}
|
||||
env:
|
||||
FORCE_OPTION: ${{ github.event.inputs.force_upload == 'true' && '--force' || '' }}
|
||||
run: conan upload "*" --remote="${CONAN_REMOTE_NAME}" --confirm ${FORCE_OPTION}
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -75,6 +75,9 @@ DerivedData
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
|
||||
# Direnv's directory
|
||||
/.direnv
|
||||
|
||||
|
||||
@@ -6,6 +6,13 @@ For info about how [API versioning](https://xrpl.org/request-formatting.html#api
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Modifications to `tx` and `account_tx`
|
||||
|
||||
In API version 2, the `tx_json` field in `tx` and `account_tx` responses includes server-added lower-case fields (`date`, `ledger_index`, and `ctid`) that are not part of the canonical signed transaction. In API version 3, these fields are removed from `tx_json` and are only present at the top-level result object.
|
||||
|
||||
- **Before (API v2)**: The `tx_json` object in the response contained `date`, `ledger_index`, and `ctid` fields alongside the canonical PascalCase transaction fields.
|
||||
- **After (API v3)**: The `tx_json` object contains only the canonical signed transaction fields. The `date`, `ledger_index`, and `ctid` fields appear exclusively at the top-level result object.
|
||||
|
||||
### Modifications to `amm_info`
|
||||
|
||||
The order of error checks has been changed to provide more specific error messages. ([#4924](https://github.com/XRPLF/rippled/pull/4924))
|
||||
|
||||
@@ -88,7 +88,6 @@ find_package(ed25519 REQUIRED)
|
||||
find_package(gRPC REQUIRED)
|
||||
find_package(LibArchive REQUIRED)
|
||||
find_package(lz4 REQUIRED)
|
||||
find_package(mpt-crypto REQUIRED)
|
||||
find_package(nudb REQUIRED)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
find_package(secp256k1 REQUIRED)
|
||||
@@ -101,7 +100,6 @@ target_link_libraries(
|
||||
INTERFACE
|
||||
ed25519::ed25519
|
||||
lz4::lz4
|
||||
mpt-crypto::mpt-crypto
|
||||
OpenSSL::Crypto
|
||||
OpenSSL::SSL
|
||||
secp256k1::secp256k1
|
||||
@@ -133,7 +131,6 @@ if(coverage)
|
||||
include(XrplCov)
|
||||
endif()
|
||||
|
||||
set(PROJECT_EXPORT_SET XrplExports)
|
||||
include(XrplCore)
|
||||
include(XrplInstall)
|
||||
include(XrplValidatorKeys)
|
||||
|
||||
@@ -127,26 +127,6 @@ tl;dr
|
||||
> 6. Wrap the body at 72 characters.
|
||||
> 7. Use the body to explain what and why vs. how.
|
||||
|
||||
In addition to those guidelines, please add one of the following
|
||||
prefixes to the subject line if appropriate.
|
||||
|
||||
- `fix:` - The primary purpose is to fix an existing bug.
|
||||
- `perf:` - The primary purpose is performance improvements.
|
||||
- `refactor:` - The changes refactor code without affecting
|
||||
functionality.
|
||||
- `test:` - The changes _only_ affect unit tests.
|
||||
- `docs:` - The changes _only_ affect documentation. This can
|
||||
include code comments in addition to `.md` files like this one.
|
||||
- `build:` - The changes _only_ affect the build process,
|
||||
including CMake and/or Conan settings.
|
||||
- `chore:` - Other tasks that don't affect the binary, but don't fit
|
||||
any of the other cases. e.g. formatting, git settings, updating
|
||||
Github Actions jobs.
|
||||
|
||||
Whenever possible, when updating commits after the PR is open, please
|
||||
add the PR number to the end of the subject line. e.g. `test: Add
|
||||
unit tests for Feature X (#1234)`.
|
||||
|
||||
## Pull requests
|
||||
|
||||
In general, pull requests use `develop` as the base branch.
|
||||
@@ -180,6 +160,23 @@ credibility of the existing approvals is insufficient.
|
||||
Pull requests must be merged by [squash-and-merge][squash]
|
||||
to preserve a linear history for the `develop` branch.
|
||||
|
||||
### Type of Change
|
||||
|
||||
In addition to those guidelines, please start your PR title with one of the following:
|
||||
|
||||
- `build:` - The changes _only_ affect the build process, including CMake and/or Conan settings.
|
||||
- `feat`: New feature (change which adds functionality).
|
||||
- `fix:` - The primary purpose is to fix an existing bug.
|
||||
- `docs:` - The changes _only_ affect documentation.
|
||||
- `test:` - The changes _only_ affect unit tests.
|
||||
- `ci`: Continuous Integration (changes to our CI configuration files and scripts).
|
||||
- `style`: Code style (formatting).
|
||||
- `refactor:` - The changes refactor code without affecting functionality.
|
||||
- `perf:` - The primary purpose is performance improvements.
|
||||
- `chore:` - Other tasks that don't affect the binary, but don't fit any of the other cases. e.g. `git` settings, `clang-tidy`, removing dead code, dropping support for older tooling.
|
||||
|
||||
First letter after the type prefix should be capitalized, and the type prefix should be followed by a colon and a space. e.g. `feat: Add support for Borrowing Protocol`.
|
||||
|
||||
### "Ready to merge"
|
||||
|
||||
A pull request should only have the "Ready to merge" label added when it
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
include(CMakeFindDependencyMacro)
|
||||
# need to represent system dependencies of the lib here
|
||||
#[=========================================================[
|
||||
Boost
|
||||
#]=========================================================]
|
||||
if(static OR APPLE OR MSVC)
|
||||
set(Boost_USE_STATIC_LIBS ON)
|
||||
endif()
|
||||
set(Boost_USE_MULTITHREADED ON)
|
||||
if(static OR MSVC)
|
||||
set(Boost_USE_STATIC_RUNTIME ON)
|
||||
else()
|
||||
set(Boost_USE_STATIC_RUNTIME OFF)
|
||||
endif()
|
||||
find_dependency(
|
||||
Boost
|
||||
COMPONENTS
|
||||
chrono
|
||||
container
|
||||
context
|
||||
coroutine
|
||||
date_time
|
||||
filesystem
|
||||
program_options
|
||||
regex
|
||||
system
|
||||
thread
|
||||
)
|
||||
#[=========================================================[
|
||||
OpenSSL
|
||||
#]=========================================================]
|
||||
if(NOT DEFINED OPENSSL_ROOT_DIR)
|
||||
if(DEFINED ENV{OPENSSL_ROOT})
|
||||
set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT})
|
||||
elseif(APPLE)
|
||||
find_program(homebrew brew)
|
||||
if(homebrew)
|
||||
execute_process(
|
||||
COMMAND ${homebrew} --prefix openssl
|
||||
OUTPUT_VARIABLE OPENSSL_ROOT_DIR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
file(TO_CMAKE_PATH "${OPENSSL_ROOT_DIR}" OPENSSL_ROOT_DIR)
|
||||
endif()
|
||||
|
||||
if(static OR APPLE OR MSVC)
|
||||
set(OPENSSL_USE_STATIC_LIBS ON)
|
||||
endif()
|
||||
set(OPENSSL_MSVC_STATIC_RT ON)
|
||||
find_dependency(OpenSSL REQUIRED)
|
||||
find_dependency(ZLIB)
|
||||
find_dependency(date)
|
||||
if(TARGET ZLIB::ZLIB)
|
||||
set_target_properties(
|
||||
OpenSSL::Crypto
|
||||
PROPERTIES INTERFACE_LINK_LIBRARIES ZLIB::ZLIB
|
||||
)
|
||||
endif()
|
||||
@@ -2,100 +2,38 @@
|
||||
install stuff
|
||||
#]===================================================================]
|
||||
|
||||
include(create_symbolic_link)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# If no suffix is defined for executables (e.g. Windows uses .exe but Linux
|
||||
# and macOS use none), then explicitly set it to the empty string.
|
||||
if(NOT DEFINED suffix)
|
||||
set(suffix "")
|
||||
if(is_root_project AND TARGET xrpld)
|
||||
install(
|
||||
TARGETS xrpld
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT runtime
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld"
|
||||
RENAME xrpld.cfg
|
||||
COMPONENT runtime
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt"
|
||||
DESTINATION "${CMAKE_INSTALL_SYSCONFDIR}/xrpld"
|
||||
RENAME validators.txt
|
||||
COMPONENT runtime
|
||||
)
|
||||
endif()
|
||||
|
||||
install(
|
||||
TARGETS
|
||||
common
|
||||
opts
|
||||
xrpl_boost
|
||||
xrpl_libs
|
||||
xrpl_syslibs
|
||||
xrpl.imports.main
|
||||
xrpl.libpb
|
||||
xrpl.libxrpl
|
||||
xrpl.libxrpl.basics
|
||||
xrpl.libxrpl.beast
|
||||
xrpl.libxrpl.conditions
|
||||
xrpl.libxrpl.core
|
||||
xrpl.libxrpl.crypto
|
||||
xrpl.libxrpl.git
|
||||
xrpl.libxrpl.json
|
||||
xrpl.libxrpl.rdb
|
||||
xrpl.libxrpl.ledger
|
||||
xrpl.libxrpl.net
|
||||
xrpl.libxrpl.nodestore
|
||||
xrpl.libxrpl.protocol
|
||||
xrpl.libxrpl.resource
|
||||
xrpl.libxrpl.server
|
||||
xrpl.libxrpl.shamap
|
||||
xrpl.libxrpl.tx
|
||||
antithesis-sdk-cpp
|
||||
EXPORT XrplExports
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin
|
||||
INCLUDES DESTINATION include
|
||||
TARGETS xrpl.libpb xrpl.libxrpl
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT development
|
||||
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT development
|
||||
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT development
|
||||
)
|
||||
|
||||
install(
|
||||
DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/xrpl"
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
|
||||
)
|
||||
|
||||
install(
|
||||
EXPORT XrplExports
|
||||
FILE XrplTargets.cmake
|
||||
NAMESPACE Xrpl::
|
||||
DESTINATION lib/cmake/xrpl
|
||||
)
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
XrplConfigVersion.cmake
|
||||
VERSION ${xrpld_version}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
if(is_root_project AND TARGET xrpld)
|
||||
install(TARGETS xrpld RUNTIME DESTINATION bin)
|
||||
set_target_properties(xrpld PROPERTIES INSTALL_RPATH_USE_LINK_PATH ON)
|
||||
# sample configs should not overwrite existing files
|
||||
# install if-not-exists workaround as suggested by
|
||||
# https://cmake.org/Bug/view.php?id=12646
|
||||
install(
|
||||
CODE
|
||||
"
|
||||
macro (copy_if_not_exists SRC DEST NEWNAME)
|
||||
if (NOT EXISTS \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\")
|
||||
file (INSTALL FILE_PERMISSIONS OWNER_READ OWNER_WRITE DESTINATION \"\${CMAKE_INSTALL_PREFIX}/\${DEST}\" FILES \"\${SRC}\" RENAME \"\${NEWNAME}\")
|
||||
else ()
|
||||
message (\"-- Skipping : \$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/\${DEST}/\${NEWNAME}\")
|
||||
endif ()
|
||||
endmacro()
|
||||
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/xrpld-example.cfg\" etc xrpld.cfg)
|
||||
copy_if_not_exists(\"${CMAKE_CURRENT_SOURCE_DIR}/cfg/validators-example.txt\" etc validators.txt)
|
||||
"
|
||||
)
|
||||
install(
|
||||
CODE
|
||||
"
|
||||
set(CMAKE_MODULE_PATH \"${CMAKE_MODULE_PATH}\")
|
||||
include(create_symbolic_link)
|
||||
create_symbolic_link(xrpld${suffix} \
|
||||
\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/rippled${suffix})
|
||||
"
|
||||
)
|
||||
endif()
|
||||
|
||||
install(
|
||||
FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/cmake/XrplConfig.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/XrplConfigVersion.cmake
|
||||
DESTINATION lib/cmake/xrpl
|
||||
COMPONENT development
|
||||
)
|
||||
|
||||
@@ -50,6 +50,13 @@ if(MSVC AND CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
|
||||
message(FATAL_ERROR "Visual Studio 32-bit build is not supported.")
|
||||
endif()
|
||||
|
||||
if(voidstar AND NOT is_amd64)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"The voidstar library only supported on amd64/x86_64. Detected archictecture was: ${CMAKE_SYSTEM_PROCESSOR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(APPLE AND NOT HOMEBREW)
|
||||
find_program(HOMEBREW brew)
|
||||
endif()
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
"sqlite3/3.49.1#8631739a4c9b93bd3d6b753bac548a63%1765850149.926",
|
||||
"soci/4.0.3#a9f8d773cd33e356b5879a4b0564f287%1765850149.46",
|
||||
"snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878",
|
||||
"secp256k1/0.7.1#481881709eb0bdd0185a12b912bbe8ad%1770910500.329",
|
||||
"secp256k1/0.7.1#3a61e95e220062ef32c48d019e9c81f7%1770306721.686",
|
||||
"rocksdb/10.5.1#4a197eca381a3e5ae8adf8cffa5aacd0%1765850186.86",
|
||||
"re2/20230301#ca3b241baec15bd31ea9187150e0b333%1765850148.103",
|
||||
"protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038",
|
||||
"openssl/3.5.5#05a4ac5b7323f7a329b2db1391d9941f%1770229825.601",
|
||||
"openssl/3.5.5#05a4ac5b7323f7a329b2db1391d9941f%1769599205.414",
|
||||
"nudb/2.0.9#0432758a24204da08fee953ec9ea03cb%1769436073.32",
|
||||
"mpt-crypto/0.1.0-rc2#575de3d495f539e3e5eba957b324d260%1771955268.105",
|
||||
"lz4/1.10.0#59fc63cac7f10fbe8e05c7e62c2f3504%1765850143.914",
|
||||
"libiconv/1.17#1e65319e945f2d31941a9d28cc13c058%1765842973.492",
|
||||
"libbacktrace/cci.20210118#a7691bfccd8caaf66309df196790a5a1%1765842973.03",
|
||||
@@ -32,7 +31,7 @@
|
||||
"strawberryperl/5.32.1.1#707032463aa0620fa17ec0d887f5fe41%1765850165.196",
|
||||
"protobuf/6.32.1#f481fd276fc23a33b85a3ed1e898b693%1765850161.038",
|
||||
"nasm/2.16.01#31e26f2ee3c4346ecd347911bd126904%1765850144.707",
|
||||
"msys2/cci.latest#d22fe7b2808f5fd34d0a7923ace9c54f%1770657326.649",
|
||||
"msys2/cci.latest#eea83308ad7e9023f7318c60d5a9e6cb%1770199879.083",
|
||||
"m4/1.4.19#70dc8bbb33e981d119d2acc0175cf381%1763158052.846",
|
||||
"cmake/4.2.0#ae0a44f44a1ef9ab68fd4b3e9a1f8671%1765850153.937",
|
||||
"cmake/3.31.10#313d16a1aa16bbdb2ca0792467214b76%1765850153.479",
|
||||
|
||||
@@ -31,7 +31,6 @@ class Xrpl(ConanFile):
|
||||
"ed25519/2015.03",
|
||||
"grpc/1.72.0",
|
||||
"libarchive/3.8.1",
|
||||
"mpt-crypto/0.1.0-rc2",
|
||||
"nudb/2.0.9",
|
||||
"openssl/3.5.5",
|
||||
"secp256k1/0.7.1",
|
||||
@@ -210,7 +209,6 @@ class Xrpl(ConanFile):
|
||||
"grpc::grpc++",
|
||||
"libarchive::libarchive",
|
||||
"lz4::lz4",
|
||||
"mpt-crypto::mpt-crypto",
|
||||
"nudb::nudb",
|
||||
"openssl::crypto",
|
||||
"protobuf::libprotobuf",
|
||||
|
||||
@@ -55,7 +55,6 @@ words:
|
||||
- autobridging
|
||||
- bimap
|
||||
- bindir
|
||||
- blindings
|
||||
- bookdir
|
||||
- Bougalis
|
||||
- Britto
|
||||
@@ -88,7 +87,6 @@ words:
|
||||
- daria
|
||||
- dcmake
|
||||
- dearmor
|
||||
- decryptor
|
||||
- deleteme
|
||||
- demultiplexer
|
||||
- deserializaton
|
||||
@@ -98,7 +96,6 @@ words:
|
||||
- distro
|
||||
- doxyfile
|
||||
- dxrpl
|
||||
- elgamal
|
||||
- endmacro
|
||||
- exceptioned
|
||||
- Falco
|
||||
@@ -107,7 +104,6 @@ words:
|
||||
- fmtdur
|
||||
- fsanitize
|
||||
- funclets
|
||||
- Gamal
|
||||
- gcov
|
||||
- gcovr
|
||||
- ghead
|
||||
@@ -193,7 +189,6 @@ words:
|
||||
- partitioner
|
||||
- paychan
|
||||
- paychans
|
||||
- Pedersen
|
||||
- permdex
|
||||
- perminute
|
||||
- permissioned
|
||||
@@ -229,7 +224,6 @@ words:
|
||||
- sahyadri
|
||||
- Satoshi
|
||||
- scons
|
||||
- Schnorr
|
||||
- secp
|
||||
- sendq
|
||||
- seqit
|
||||
@@ -256,7 +250,6 @@ words:
|
||||
- stvar
|
||||
- stvector
|
||||
- stxchainattestations
|
||||
- summands
|
||||
- superpeer
|
||||
- superpeers
|
||||
- takergets
|
||||
|
||||
@@ -1,484 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/STLedgerEntry.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
#include <xrpl/protocol/detail/secp256k1.h>
|
||||
|
||||
#include <secp256k1_mpt.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Bundles an ElGamal public key with its associated encrypted amount.
|
||||
*
|
||||
* Used to represent a recipient in confidential transfers, containing both
|
||||
* the recipient's ElGamal public key and the ciphertext encrypting the
|
||||
* transfer amount under that key.
|
||||
*/
|
||||
struct ConfidentialRecipient
|
||||
{
|
||||
Slice publicKey; ///< The recipient's ElGamal public key (64 bytes).
|
||||
Slice encryptedAmount; ///< The encrypted amount ciphertext (128 bytes).
|
||||
};
|
||||
|
||||
/// Holds two secp256k1 public key components representing an ElGamal ciphertext (C1, C2).
|
||||
struct EcPair
|
||||
{
|
||||
secp256k1_pubkey c1;
|
||||
secp256k1_pubkey c2;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Increments the confidential balance version counter on an MPToken.
|
||||
*
|
||||
* The version counter is used to prevent replay attacks by binding proofs
|
||||
* to a specific state of the account's confidential balance. Wraps to 0
|
||||
* on overflow (defined behavior for unsigned integers).
|
||||
*
|
||||
* @param mptoken The MPToken ledger entry to update.
|
||||
*/
|
||||
inline void
|
||||
incrementConfidentialVersion(STObject& mptoken)
|
||||
{
|
||||
// Retrieve current version and increment.
|
||||
// Unsigned integer overflow is defined behavior in C++ (wraps to 0),
|
||||
// which is acceptable here.
|
||||
mptoken[sfConfidentialBalanceVersion] =
|
||||
mptoken[~sfConfidentialBalanceVersion].value_or(0u) + 1u;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds common fields to a serializer for ZKP context hash generation.
|
||||
*
|
||||
* Serializes the transaction type, account, issuance ID and sequence/ticket number
|
||||
* into the provided serializer. These fields form the base of all context
|
||||
* hashes used in zero-knowledge proofs.
|
||||
*
|
||||
* @param s The serializer to append fields to.
|
||||
* @param txType The transaction type identifier.
|
||||
* @param account The account ID of the transaction sender.
|
||||
* @param issuanceID The MPToken Issuance ID.
|
||||
* @param sequence The transaction sequence number or ticket number.
|
||||
*/
|
||||
void
|
||||
addCommonZKPFields(
|
||||
Serializer& s,
|
||||
std::uint16_t txType,
|
||||
AccountID const& account,
|
||||
std::uint32_t sequence,
|
||||
uint192 const& issuanceID);
|
||||
|
||||
/**
|
||||
* @brief Generates the context hash for ConfidentialMPTSend transactions.
|
||||
*
|
||||
* Creates a unique 256-bit hash that binds the zero-knowledge proofs to
|
||||
* this specific send transaction, preventing proof reuse across transactions.
|
||||
*
|
||||
* @param account The sender's account ID.
|
||||
* @param issuanceID The MPToken Issuance ID.
|
||||
* @param sequence The transaction sequence number or ticket number.
|
||||
* @param destination The destination account ID.
|
||||
* @param version The sender's confidential balance version.
|
||||
* @return A 256-bit context hash unique to this transaction.
|
||||
*/
|
||||
uint256
|
||||
getSendContextHash(
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence,
|
||||
AccountID const& destination,
|
||||
std::uint32_t version);
|
||||
|
||||
/**
|
||||
* @brief Generates the context hash for ConfidentialMPTClawback transactions.
|
||||
*
|
||||
* Creates a unique 256-bit hash that binds the equality proof to this
|
||||
* specific clawback transaction.
|
||||
*
|
||||
* @param account The issuer's account ID.
|
||||
* @param issuanceID The MPToken Issuance ID.
|
||||
* @param sequence The transaction sequence number or ticket number.
|
||||
* @param holder The holder's account ID being clawed back from.
|
||||
* @return A 256-bit context hash unique to this transaction.
|
||||
*/
|
||||
uint256
|
||||
getClawbackContextHash(
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence,
|
||||
AccountID const& holder);
|
||||
|
||||
/**
|
||||
* @brief Generates the context hash for ConfidentialMPTConvert transactions.
|
||||
*
|
||||
* Creates a unique 256-bit hash that binds the Schnorr proof (for key
|
||||
* registration) to this specific convert transaction.
|
||||
*
|
||||
* @param account The holder's account ID.
|
||||
* @param issuanceID The MPToken Issuance ID.
|
||||
* @param sequence The transaction sequence number or a ticket number.
|
||||
* @return A 256-bit context hash unique to this transaction.
|
||||
*/
|
||||
uint256
|
||||
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence);
|
||||
|
||||
/**
|
||||
* @brief Generates the context hash for ConfidentialMPTConvertBack transactions.
|
||||
*
|
||||
* Creates a unique 256-bit hash that binds the zero-knowledge proofs to
|
||||
* this specific convert-back transaction.
|
||||
*
|
||||
* @param account The holder's account ID.
|
||||
* @param issuanceID The MPToken Issuance ID.
|
||||
* @param sequence The transaction sequence number or a ticket number.
|
||||
* @param version The holder's confidential balance version.
|
||||
* @return A 256-bit context hash unique to this transaction.
|
||||
*/
|
||||
uint256
|
||||
getConvertBackContextHash(
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t version);
|
||||
|
||||
/**
|
||||
* @brief Parses an ElGamal ciphertext into two secp256k1 public key components.
|
||||
*
|
||||
* Breaks a 66-byte encrypted amount (two 33-byte compressed EC points) into
|
||||
* a pair containing (C1, C2) for use in cryptographic operations.
|
||||
*
|
||||
* @param buffer The 66-byte buffer containing the compressed ciphertext.
|
||||
* @return The parsed pair (c1, c2) if successful, std::nullopt if the buffer is invalid.
|
||||
*/
|
||||
std::optional<EcPair>
|
||||
makeEcPair(Slice const& buffer);
|
||||
|
||||
/**
|
||||
* @brief Serializes an EcPair into compressed form.
|
||||
*
|
||||
* Converts an EcPair (C1, C2) back into a 66-byte buffer containing
|
||||
* two 33-byte compressed EC points.
|
||||
*
|
||||
* @param pair The EcPair to serialize.
|
||||
* @return The 66-byte buffer, or std::nullopt if serialization fails.
|
||||
*/
|
||||
std::optional<Buffer>
|
||||
serializeEcPair(EcPair const& pair);
|
||||
|
||||
/**
|
||||
* @brief Verifies that a buffer contains two valid, parsable EC public keys.
|
||||
*
|
||||
* @param buffer The input buffer containing two concatenated components.
|
||||
* @return true if both components can be parsed successfully, false otherwise.
|
||||
*/
|
||||
bool
|
||||
isValidCiphertext(Slice const& buffer);
|
||||
|
||||
/**
|
||||
* @brief Verifies that a buffer contains a valid, parsable compressed EC point.
|
||||
*
|
||||
* Can be used to validate both compressed public keys and Pedersen commitments.
|
||||
* Fails early if the prefix byte is not 0x02 or 0x03.
|
||||
*
|
||||
* @param buffer The input buffer containing a compressed EC point (33 bytes).
|
||||
* @return true if the point can be parsed successfully, false otherwise.
|
||||
*/
|
||||
bool
|
||||
isValidCompressedECPoint(Slice const& buffer);
|
||||
|
||||
/**
|
||||
* @brief Homomorphically adds two ElGamal ciphertexts.
|
||||
*
|
||||
* Uses the additive homomorphic property of ElGamal encryption to compute
|
||||
* Enc(a + b) from Enc(a) and Enc(b) without decryption.
|
||||
*
|
||||
* @param a The first ciphertext (66 bytes).
|
||||
* @param b The second ciphertext (66 bytes).
|
||||
* @return The resulting ciphertext Enc(a + b), or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<Buffer>
|
||||
homomorphicAdd(Slice const& a, Slice const& b);
|
||||
|
||||
/**
|
||||
* @brief Homomorphically subtracts two ElGamal ciphertexts.
|
||||
*
|
||||
* Uses the additive homomorphic property of ElGamal encryption to compute
|
||||
* Enc(a - b) from Enc(a) and Enc(b) without decryption.
|
||||
*
|
||||
* @param a The minuend ciphertext (66 bytes).
|
||||
* @param b The subtrahend ciphertext (66 bytes).
|
||||
* @return The resulting ciphertext Enc(a - b), or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<Buffer>
|
||||
homomorphicSubtract(Slice const& a, Slice const& b);
|
||||
|
||||
/**
|
||||
* @brief Encrypts an amount using ElGamal encryption.
|
||||
*
|
||||
* Produces a ciphertext C = (C1, C2) where C1 = r*G and C2 = m*G + r*Pk,
|
||||
* using the provided blinding factor r.
|
||||
*
|
||||
* @param amt The plaintext amount to encrypt.
|
||||
* @param pubKeySlice The recipient's ElGamal public key (64 bytes).
|
||||
* @param blindingFactor The 32-byte randomness used as blinding factor r.
|
||||
* @return The 66-byte ciphertext, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<Buffer>
|
||||
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor);
|
||||
|
||||
/**
|
||||
* @brief Generates the canonical zero encryption for a specific MPToken.
|
||||
*
|
||||
* Creates a deterministic encryption of zero that is unique to the account
|
||||
* and MPT issuance. Used to initialize confidential balance fields.
|
||||
*
|
||||
* @param pubKeySlice The holder's ElGamal public key (64 bytes).
|
||||
* @param account The account ID of the token holder.
|
||||
* @param mptId The MPToken Issuance ID.
|
||||
* @return The 66-byte canonical zero ciphertext, or std::nullopt on failure.
|
||||
*/
|
||||
std::optional<Buffer>
|
||||
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId);
|
||||
|
||||
/**
|
||||
* @brief Verifies a Schnorr proof of knowledge of an ElGamal private key.
|
||||
*
|
||||
* Proves that the submitter knows the secret key corresponding to the
|
||||
* provided public key, without revealing the secret key itself.
|
||||
*
|
||||
* @param pubKeySlice The ElGamal public key (64 bytes).
|
||||
* @param proofSlice The Schnorr proof (65 bytes).
|
||||
* @param contextHash The 256-bit context hash binding the proof.
|
||||
* @return tesSUCCESS if valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies that a ciphertext correctly encrypts a revealed amount.
|
||||
*
|
||||
* Given the plaintext amount and blinding factor, verifies that the
|
||||
* ciphertext was correctly constructed using ElGamal encryption.
|
||||
*
|
||||
* @param amount The revealed plaintext amount.
|
||||
* @param blindingFactor The 32-byte blinding factor used in encryption.
|
||||
* @param pubKeySlice The recipient's ElGamal public key (64 bytes).
|
||||
* @param ciphertext The ciphertext to verify (66 bytes).
|
||||
* @return tesSUCCESS if the encryption is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyElGamalEncryption(
|
||||
uint64_t const amount,
|
||||
Slice const& blindingFactor,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext);
|
||||
|
||||
/**
|
||||
* @brief Validates the format of encrypted amount fields in a transaction.
|
||||
*
|
||||
* Checks that all ciphertext fields in the transaction object have the
|
||||
* correct length and contain valid EC points. This function is only used
|
||||
* by ConfidentialMPTConvert and ConfidentialMPTConvertBack transactions.
|
||||
*
|
||||
* @param object The transaction object containing encrypted amount fields.
|
||||
* @return tesSUCCESS if all formats are valid, temMALFORMED if required fields
|
||||
* are missing, or temBAD_CIPHERTEXT if format validation fails.
|
||||
*/
|
||||
NotTEC
|
||||
checkEncryptedAmountFormat(STObject const& object);
|
||||
|
||||
/**
|
||||
* @brief Verifies revealed amount encryptions for all recipients.
|
||||
*
|
||||
* Validates that the same amount was correctly encrypted for the holder,
|
||||
* issuer, and optionally the auditor using their respective public keys.
|
||||
*
|
||||
* @param amount The revealed plaintext amount.
|
||||
* @param blindingFactor The 32-byte blinding factor used in all encryptions.
|
||||
* @param holder The holder's public key and encrypted amount.
|
||||
* @param issuer The issuer's public key and encrypted amount.
|
||||
* @param auditor Optional auditor's public key and encrypted amount.
|
||||
* @return tesSUCCESS if all encryptions are valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyRevealedAmount(
|
||||
uint64_t const amount,
|
||||
Slice const& blindingFactor,
|
||||
ConfidentialRecipient const& holder,
|
||||
ConfidentialRecipient const& issuer,
|
||||
std::optional<ConfidentialRecipient> const& auditor);
|
||||
|
||||
/**
|
||||
* @brief Returns the number of recipients in a confidential transfer.
|
||||
*
|
||||
* Returns 4 if an auditor is present (sender, destination, issuer, auditor),
|
||||
* or 3 if no auditor (sender, destination, issuer).
|
||||
*
|
||||
* @param hasAuditor Whether the issuance has an auditor configured.
|
||||
* @return The number of recipients (3 or 4).
|
||||
*/
|
||||
constexpr std::size_t
|
||||
getConfidentialRecipientCount(bool hasAuditor)
|
||||
{
|
||||
return hasAuditor ? 4 : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verifies a multi-ciphertext equality proof.
|
||||
*
|
||||
* Proves that all ciphertexts in the recipients vector encrypt the same
|
||||
* plaintext amount, without revealing the amount itself.
|
||||
*
|
||||
* @param proof The zero-knowledge proof bytes.
|
||||
* @param recipients Vector of recipients with their public keys and ciphertexts.
|
||||
* @param nRecipients The number of recipients (must match recipients.size()).
|
||||
* @param contextHash The 256-bit context hash binding the proof.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyMultiCiphertextEqualityProof(
|
||||
Slice const& proof,
|
||||
std::vector<ConfidentialRecipient> const& recipients,
|
||||
std::size_t const nRecipients,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies a clawback equality proof.
|
||||
*
|
||||
* Proves that the issuer knows the exact amount encrypted in the holder's
|
||||
* balance ciphertext. Used in ConfidentialMPTClawback to verify the issuer
|
||||
* can decrypt the balance using their private key.
|
||||
*
|
||||
* @param amount The revealed plaintext amount.
|
||||
* @param proof The zero-knowledge proof bytes.
|
||||
* @param pubKeySlice The issuer's ElGamal public key (64 bytes).
|
||||
* @param ciphertext The issuer's encrypted balance on the holder's account (66 bytes).
|
||||
* @param contextHash The 256-bit context hash binding the proof.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyClawbackEqualityProof(
|
||||
uint64_t const amount,
|
||||
Slice const& proof,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Generates a cryptographically secure 32-byte blinding factor.
|
||||
*
|
||||
* Produces random bytes suitable for use as an ElGamal blinding factor
|
||||
* or Pedersen commitment randomness.
|
||||
*
|
||||
* @return A 32-byte buffer containing the random blinding factor.
|
||||
*/
|
||||
Buffer
|
||||
generateBlindingFactor();
|
||||
|
||||
/**
|
||||
* @brief Verifies the cryptographic link between an ElGamal Ciphertext and a
|
||||
* Pedersen Commitment for a transaction Amount.
|
||||
*
|
||||
* It proves that the ElGamal ciphertext `encAmt` encrypts the same value `m`
|
||||
* as the Pedersen Commitment `pcmSlice`, using the randomness `r`.
|
||||
* Proves Enc(m) <-> Pcm(m)
|
||||
*
|
||||
* @param proof The Zero Knowledge Proof bytes.
|
||||
* @param encAmt The ElGamal ciphertext of the amount (C1, C2).
|
||||
* @param pubKeySlice The sender's public key.
|
||||
* @param pcmSlice The Pedersen Commitment to the amount.
|
||||
* @param contextHash The unique context hash for this transaction.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyAmountPcmLinkage(
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies the cryptographic link between an ElGamal Ciphertext and a
|
||||
* Pedersen Commitment for an account Balance.
|
||||
*
|
||||
* It proves that the ElGamal ciphertext `encAmt` encrypts the same value `b`
|
||||
* as the Pedersen Commitment `pcmSlice`, using the secret key `s`.
|
||||
* Proves Enc(b) <-> Pcm(b)
|
||||
*
|
||||
* Note: Swaps arguments (Pk <-> C1) to accommodate the different algebraic
|
||||
* structure.
|
||||
*
|
||||
* @param proof The Zero Knowledge Proof bytes.
|
||||
* @param encAmt The ElGamal ciphertext of the balance (C1, C2).
|
||||
* @param pubKeySlice The sender's public key.
|
||||
* @param pcmSlice The Pedersen Commitment to the balance.
|
||||
* @param contextHash The unique context hash for this transaction.
|
||||
* @return tesSUCCESS if the proof is valid, or an error code otherwise.
|
||||
*/
|
||||
TER
|
||||
verifyBalancePcmLinkage(
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Verifies an aggregated Bulletproof range proof.
|
||||
*
|
||||
* This function verifies that all commitments in commitment_C_vec commit
|
||||
* to values within the valid 64-bit range [0, 2^64 - 1].
|
||||
*
|
||||
* @param proof The serialized Bulletproof proof.
|
||||
* @param compressedCommitments Vector of compressed Pedersen commitments (each 33 bytes).
|
||||
* @param contextHash The unique context hash for this transaction.
|
||||
* @return tesSUCCESS if the proof is valid, tecBAD_PROOF if verification
|
||||
* fails, or tecINTERNAL for internal errors.
|
||||
*/
|
||||
TER
|
||||
verifyAggregatedBulletproof(
|
||||
Slice const& proof,
|
||||
std::vector<Slice> const& compressedCommitments,
|
||||
uint256 const& contextHash);
|
||||
|
||||
/**
|
||||
* @brief Computes the remainder commitment for ConfidentialMPTSend.
|
||||
*
|
||||
* Given a balance commitment PC_bal = m_bal*G + rho_bal*H and an amount
|
||||
* commitment PC_amt = m_amt*G + rho_amt*H, this function computes:
|
||||
* PC_rem = PC_bal - PC_amt = (m_bal - m_amt)*G + (rho_bal - rho_amt)*H
|
||||
*
|
||||
* This derived commitment is used in an aggregated range proof to ensure
|
||||
* the sender maintains a non-negative balance (m_bal - m_amt >= 0).
|
||||
*
|
||||
* @param balanceCommitment The compressed Pedersen commitment to the balance (33 bytes).
|
||||
* @param amountCommitment The compressed Pedersen commitment to the amount (33 bytes).
|
||||
* @param out Output buffer for the resulting remainder commitment (33 bytes).
|
||||
* @return tesSUCCESS on success, tecINTERNAL on failure.
|
||||
*/
|
||||
TER
|
||||
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment, Buffer& out);
|
||||
|
||||
/**
|
||||
* @brief Computes the remainder commitment for ConvertBack.
|
||||
*
|
||||
* Given a Pedersen commitment PC = m*G + rho*H, this function computes
|
||||
* PC_rem = PC - amount*G = (m - amount)*G + rho*H
|
||||
*
|
||||
* @param commitment The compressed Pedersen commitment (33 bytes).
|
||||
* @param amount The amount to subtract (must be non-zero).
|
||||
* @param out Output buffer for the resulting commitment (33 bytes).
|
||||
* @return tesSUCCESS on success, tecINTERNAL on failure or if amount is 0.
|
||||
*/
|
||||
TER
|
||||
computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& out);
|
||||
} // namespace xrpl
|
||||
@@ -173,8 +173,7 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LSF_FLAG(lsfMPTCanEscrow, 0x00000008) \
|
||||
LSF_FLAG(lsfMPTCanTrade, 0x00000010) \
|
||||
LSF_FLAG(lsfMPTCanTransfer, 0x00000020) \
|
||||
LSF_FLAG(lsfMPTCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsfMPTCanConfidentialAmount, 0x00000080)) \
|
||||
LSF_FLAG(lsfMPTCanClawback, 0x00000040)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPTokenIssuanceMutable, \
|
||||
LSF_FLAG(lsmfMPTCanMutateCanLock, 0x00000002) \
|
||||
@@ -184,8 +183,7 @@ enum LedgerEntryType : std::uint16_t {
|
||||
LSF_FLAG(lsmfMPTCanMutateCanTransfer, 0x00000020) \
|
||||
LSF_FLAG(lsmfMPTCanMutateCanClawback, 0x00000040) \
|
||||
LSF_FLAG(lsmfMPTCanMutateMetadata, 0x00010000) \
|
||||
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000) \
|
||||
LSF_FLAG(lsmfMPTCannotMutateCanConfidentialAmount, 0x00040000)) \
|
||||
LSF_FLAG(lsmfMPTCanMutateTransferFee, 0x00020000)) \
|
||||
\
|
||||
LEDGER_OBJECT(MPToken, \
|
||||
LSF_FLAG2(lsfMPTLocked, 0x00000001) \
|
||||
|
||||
@@ -307,49 +307,4 @@ std::size_t constexpr permissionMaxSize = 10;
|
||||
/** The maximum number of transactions that can be in a batch. */
|
||||
std::size_t constexpr maxBatchTxCount = 8;
|
||||
|
||||
/** EC ElGamal ciphertext length 33-byte */
|
||||
std::size_t constexpr ecGamalEncryptedLength = 33;
|
||||
|
||||
/** EC ElGamal ciphertext length: two 33-byte components concatenated */
|
||||
std::size_t constexpr ecGamalEncryptedTotalLength = ecGamalEncryptedLength * 2;
|
||||
|
||||
/** Length of equality ZKProof in bytes */
|
||||
std::size_t constexpr ecEqualityProofLength = 98;
|
||||
|
||||
/** Length of EC point (compressed) */
|
||||
std::size_t constexpr compressedECPointLength = 33;
|
||||
|
||||
/** Length of EC public key (compressed) */
|
||||
std::size_t constexpr ecPubKeyLength = compressedECPointLength;
|
||||
|
||||
/** Length of EC private key in bytes */
|
||||
std::size_t constexpr ecPrivKeyLength = 32;
|
||||
|
||||
/** Length of the EC blinding factor in bytes */
|
||||
std::size_t constexpr ecBlindingFactorLength = 32;
|
||||
|
||||
/** Length of Schnorr ZKProof for public key registration in bytes */
|
||||
std::size_t constexpr ecSchnorrProofLength = 65;
|
||||
|
||||
/** Length of ElGamal ciphertext equality proof in bytes */
|
||||
std::size_t constexpr ecCiphertextEqualityProofLength = 261;
|
||||
|
||||
/** Length of ElGamal Pedersen linkage proof in bytes */
|
||||
std::size_t constexpr ecPedersenProofLength = 195;
|
||||
|
||||
/** Length of Pedersen Commitment (compressed) */
|
||||
std::size_t constexpr ecPedersenCommitmentLength = compressedECPointLength;
|
||||
|
||||
/** Length of single bulletproof (range proof for 1 commitment) in bytes */
|
||||
std::size_t constexpr ecSingleBulletproofLength = 688;
|
||||
|
||||
/** Length of double bulletproof (range proof for 2 commitments) in bytes */
|
||||
std::size_t constexpr ecDoubleBulletproofLength = 754;
|
||||
|
||||
/** Compressed EC point prefix for even y-coordinate */
|
||||
static constexpr std::uint8_t ecCompressedPrefixEvenY = 0x02;
|
||||
|
||||
/** Compressed EC point prefix for odd y-coordinate */
|
||||
static constexpr std::uint8_t ecCompressedPrefixOddY = 0x03;
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -23,9 +23,10 @@ struct JsonOptions
|
||||
none = 0b0000'0000,
|
||||
include_date = 0b0000'0001,
|
||||
disable_API_prior_V2 = 0b0000'0010,
|
||||
disable_API_prior_V3 = 0b0000'0100,
|
||||
|
||||
// IMPORTANT `_all` must be union of all of the above; see also operator~
|
||||
_all = 0b0000'0011
|
||||
_all = 0b0000'0111
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
|
||||
@@ -121,7 +121,6 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
temBAD_CIPHERTEXT,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -347,7 +346,6 @@ enum TECcodes : TERUnderlyingType {
|
||||
// backward compatibility with historical data on non-prod networks, can be
|
||||
// reclaimed after those networks reset.
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecBAD_PROOF = 199,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -138,8 +138,7 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal;
|
||||
TF_FLAG(tfMPTCanEscrow, lsfMPTCanEscrow) \
|
||||
TF_FLAG(tfMPTCanTrade, lsfMPTCanTrade) \
|
||||
TF_FLAG(tfMPTCanTransfer, lsfMPTCanTransfer) \
|
||||
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback) \
|
||||
TF_FLAG(tfMPTCanConfidentialAmount, lsfMPTCanConfidentialAmount), \
|
||||
TF_FLAG(tfMPTCanClawback, lsfMPTCanClawback), \
|
||||
MASK_ADJ(0)) \
|
||||
\
|
||||
TRANSACTION(MPTokenAuthorize, \
|
||||
@@ -348,12 +347,10 @@ inline constexpr FlagValue tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTrans
|
||||
inline constexpr FlagValue tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback;
|
||||
inline constexpr FlagValue tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata;
|
||||
inline constexpr FlagValue tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee;
|
||||
inline constexpr FlagValue tmfMPTCannotMutateCanConfidentialAmount =
|
||||
lsmfMPTCannotMutateCanConfidentialAmount;
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask = ~(
|
||||
tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
|
||||
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
|
||||
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee | tmfMPTCannotMutateCanConfidentialAmount);
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceCreateMutableMask =
|
||||
~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow |
|
||||
tmfMPTCanMutateCanTrade | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback |
|
||||
tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee);
|
||||
|
||||
// MPTokenIssuanceSet MutableFlags:
|
||||
// Set or Clear flags.
|
||||
@@ -370,13 +367,10 @@ inline constexpr FlagValue tmfMPTSetCanTransfer = 0x00000100;
|
||||
inline constexpr FlagValue tmfMPTClearCanTransfer = 0x00000200;
|
||||
inline constexpr FlagValue tmfMPTSetCanClawback = 0x00000400;
|
||||
inline constexpr FlagValue tmfMPTClearCanClawback = 0x00000800;
|
||||
inline constexpr FlagValue tmfMPTSetCanConfidentialAmount = 0x00001000;
|
||||
inline constexpr FlagValue tmfMPTClearCanConfidentialAmount = 0x00002000;
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask =
|
||||
~(tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
|
||||
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
|
||||
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback |
|
||||
tmfMPTClearCanClawback | tmfMPTSetCanConfidentialAmount | tmfMPTClearCanConfidentialAmount);
|
||||
inline constexpr FlagValue tmfMPTokenIssuanceSetMutableMask = ~(
|
||||
tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth |
|
||||
tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade |
|
||||
tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanClawback | tmfMPTClearCanClawback);
|
||||
|
||||
// Prior to fixRemoveNFTokenAutoTrustLine, transfer of an NFToken between accounts allowed a
|
||||
// TrustLine to be added to the issuer of that token without explicit permission from that issuer.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
// Add new amendments to the top of this list.
|
||||
// Keep it sorted in reverse chronological order.
|
||||
XRPL_FEATURE(ConfidentialTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
XRPL_FIX (PermissionedDomainInvariant, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -385,41 +385,32 @@ LEDGER_ENTRY(ltAMM, 0x0079, AMM, amm, ({
|
||||
\sa keylet::mptIssuance
|
||||
*/
|
||||
LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfTransferFee, soeDEFAULT},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfAssetScale, soeDEFAULT},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
{sfOutstandingAmount, soeREQUIRED},
|
||||
{sfLockedAmount, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfMutableFlags, soeDEFAULT},
|
||||
{sfIssuerEncryptionKey, soeOPTIONAL},
|
||||
{sfAuditorEncryptionKey, soeOPTIONAL},
|
||||
{sfConfidentialOutstandingAmount, soeDEFAULT},
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfTransferFee, soeDEFAULT},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfAssetScale, soeDEFAULT},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
{sfOutstandingAmount, soeREQUIRED},
|
||||
{sfLockedAmount, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfMutableFlags, soeDEFAULT},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks MPToken
|
||||
\sa keylet::mptoken
|
||||
*/
|
||||
LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeDEFAULT},
|
||||
{sfLockedAmount, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfConfidentialBalanceInbox, soeOPTIONAL},
|
||||
{sfConfidentialBalanceSpending, soeOPTIONAL},
|
||||
{sfConfidentialBalanceVersion, soeDEFAULT},
|
||||
{sfIssuerEncryptedBalance, soeOPTIONAL},
|
||||
{sfAuditorEncryptedBalance, soeOPTIONAL},
|
||||
{sfHolderEncryptionKey, soeOPTIONAL},
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeDEFAULT},
|
||||
{sfLockedAmount, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks Oracle
|
||||
|
||||
@@ -114,7 +114,6 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi
|
||||
TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips)
|
||||
TYPED_SFIELD(sfConfidentialBalanceVersion, UINT32, 69)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -148,7 +147,6 @@ TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
||||
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
TYPED_SFIELD(sfVaultNode, UINT64, 30)
|
||||
TYPED_SFIELD(sfLoanBrokerNode, UINT64, 31)
|
||||
TYPED_SFIELD(sfConfidentialOutstandingAmount, UINT64, 32, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
|
||||
// 128-bit
|
||||
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
||||
@@ -299,22 +297,6 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
|
||||
TYPED_SFIELD(sfProvider, VL, 29)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 31)
|
||||
TYPED_SFIELD(sfConfidentialBalanceInbox, VL, 32)
|
||||
TYPED_SFIELD(sfConfidentialBalanceSpending, VL, 33)
|
||||
TYPED_SFIELD(sfIssuerEncryptedBalance, VL, 34)
|
||||
TYPED_SFIELD(sfIssuerEncryptionKey, VL, 35)
|
||||
TYPED_SFIELD(sfHolderEncryptionKey, VL, 36)
|
||||
TYPED_SFIELD(sfZKProof, VL, 37)
|
||||
TYPED_SFIELD(sfHolderEncryptedAmount, VL, 38)
|
||||
TYPED_SFIELD(sfIssuerEncryptedAmount, VL, 39)
|
||||
TYPED_SFIELD(sfSenderEncryptedAmount, VL, 40)
|
||||
TYPED_SFIELD(sfDestinationEncryptedAmount, VL, 41)
|
||||
TYPED_SFIELD(sfAuditorEncryptedBalance, VL, 42)
|
||||
TYPED_SFIELD(sfAuditorEncryptedAmount, VL, 43)
|
||||
TYPED_SFIELD(sfAuditorEncryptionKey, VL, 44)
|
||||
TYPED_SFIELD(sfBlindingFactor, VL, 45)
|
||||
TYPED_SFIELD(sfAmountCommitment, VL, 46)
|
||||
TYPED_SFIELD(sfBalanceCommitment, VL, 47)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
|
||||
@@ -42,7 +42,7 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/escrow/Escrow.h>
|
||||
# include <xrpl/tx/transactors/escrow/EscrowCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
Delegation::delegable,
|
||||
@@ -58,6 +58,9 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/escrow/EscrowFinish.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
|
||||
Delegation::delegable,
|
||||
uint256{},
|
||||
@@ -94,7 +97,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/escrow/Escrow.h>
|
||||
# include <xrpl/tx/transactors/escrow/EscrowCancel.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
|
||||
Delegation::delegable,
|
||||
@@ -180,7 +183,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet,
|
||||
|
||||
/** This transaction type creates a new unidirectional XRP payment channel. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/payment_channel/PayChan.h>
|
||||
# include <xrpl/tx/transactors/payment_channel/PayChanCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
Delegation::delegable,
|
||||
@@ -196,6 +199,9 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type funds an existing unidirectional XRP payment channel. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/payment_channel/PayChanFund.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
|
||||
Delegation::delegable,
|
||||
uint256{},
|
||||
@@ -207,6 +213,9 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund,
|
||||
}))
|
||||
|
||||
/** This transaction type submits a claim against an existing unidirectional payment channel. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/payment_channel/PayChanClaim.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim,
|
||||
Delegation::delegable,
|
||||
uint256{},
|
||||
@@ -617,7 +626,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/did/DID.h>
|
||||
# include <xrpl/tx/transactors/did/DIDSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet,
|
||||
Delegation::delegable,
|
||||
@@ -630,6 +639,9 @@ TRANSACTION(ttDID_SET, 49, DIDSet,
|
||||
}))
|
||||
|
||||
/** This transaction type deletes a DID */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/did/DIDDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttDID_DELETE, 50, DIDDelete,
|
||||
Delegation::delegable,
|
||||
featureDID,
|
||||
@@ -722,8 +734,6 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet,
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfTransferFee, soeOPTIONAL},
|
||||
{sfMutableFlags, soeOPTIONAL},
|
||||
{sfIssuerEncryptionKey, soeOPTIONAL},
|
||||
{sfAuditorEncryptionKey, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type authorizes a MPToken instance */
|
||||
@@ -741,7 +751,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
|
||||
|
||||
/** This transaction type create an Credential instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/credentials/Credentials.h>
|
||||
# include <xrpl/tx/transactors/credentials/CredentialCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
Delegation::delegable,
|
||||
@@ -755,6 +765,9 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
}))
|
||||
|
||||
/** This transaction type accept an Credential object */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/credentials/CredentialAccept.h>
|
||||
#endif
|
||||
TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
|
||||
Delegation::delegable,
|
||||
featureCredentials,
|
||||
@@ -765,6 +778,9 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept,
|
||||
}))
|
||||
|
||||
/** This transaction type delete an Credential object */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/credentials/CredentialDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete,
|
||||
Delegation::delegable,
|
||||
featureCredentials,
|
||||
@@ -832,7 +848,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet,
|
||||
# include <xrpl/tx/transactors/vault/VaultCreate.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureSingleAssetVault,
|
||||
createPseudoAcct | createMPTIssuance | mustModifyVault,
|
||||
({
|
||||
@@ -850,7 +866,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate,
|
||||
# include <xrpl/tx/transactors/vault/VaultSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_SET, 66, VaultSet,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureSingleAssetVault,
|
||||
mustModifyVault,
|
||||
({
|
||||
@@ -865,7 +881,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet,
|
||||
# include <xrpl/tx/transactors/vault/VaultDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureSingleAssetVault,
|
||||
mustDeleteAcct | destroyMPTIssuance | mustModifyVault,
|
||||
({
|
||||
@@ -877,7 +893,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete,
|
||||
# include <xrpl/tx/transactors/vault/VaultDeposit.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureSingleAssetVault,
|
||||
mayAuthorizeMPT | mustModifyVault,
|
||||
({
|
||||
@@ -890,7 +906,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit,
|
||||
# include <xrpl/tx/transactors/vault/VaultWithdraw.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureSingleAssetVault,
|
||||
mayDeleteMPT | mayAuthorizeMPT | mustModifyVault,
|
||||
({
|
||||
@@ -905,7 +921,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw,
|
||||
# include <xrpl/tx/transactors/vault/VaultClawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureSingleAssetVault,
|
||||
mayDeleteMPT | mustModifyVault,
|
||||
({
|
||||
@@ -934,7 +950,7 @@ TRANSACTION(ttBATCH, 71, Batch,
|
||||
# include <xrpl/tx/transactors/lending/LoanBrokerSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
createPseudoAcct | mayAuthorizeMPT, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
@@ -951,7 +967,7 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet,
|
||||
# include <xrpl/tx/transactors/lending/LoanBrokerDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
mustDeleteAcct | mayAuthorizeMPT, ({
|
||||
{sfLoanBrokerID, soeREQUIRED},
|
||||
@@ -962,7 +978,7 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete,
|
||||
# include <xrpl/tx/transactors/lending/LoanBrokerCoverDeposit.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
noPriv, ({
|
||||
{sfLoanBrokerID, soeREQUIRED},
|
||||
@@ -974,7 +990,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit,
|
||||
# include <xrpl/tx/transactors/lending/LoanBrokerCoverWithdraw.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
mayAuthorizeMPT, ({
|
||||
{sfLoanBrokerID, soeREQUIRED},
|
||||
@@ -989,7 +1005,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw,
|
||||
# include <xrpl/tx/transactors/lending/LoanBrokerCoverClawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
noPriv, ({
|
||||
{sfLoanBrokerID, soeOPTIONAL},
|
||||
@@ -1001,7 +1017,7 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback,
|
||||
# include <xrpl/tx/transactors/lending/LoanSet.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_SET, 80, LoanSet,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
mayAuthorizeMPT | mustModifyVault, ({
|
||||
{sfLoanBrokerID, soeREQUIRED},
|
||||
@@ -1028,7 +1044,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
|
||||
# include <xrpl/tx/transactors/lending/LoanDelete.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
noPriv, ({
|
||||
{sfLoanID, soeREQUIRED},
|
||||
@@ -1039,7 +1055,7 @@ TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
|
||||
# include <xrpl/tx/transactors/lending/LoanManage.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
// All of the LoanManage options will modify the vault, but the
|
||||
// transaction can succeed without options, essentially making it
|
||||
@@ -1053,97 +1069,13 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
|
||||
# include <xrpl/tx/transactors/lending/LoanPay.h>
|
||||
#endif
|
||||
TRANSACTION(ttLOAN_PAY, 84, LoanPay,
|
||||
Delegation::delegable,
|
||||
Delegation::notDelegable,
|
||||
featureLendingProtocol,
|
||||
mayAuthorizeMPT | mustModifyVault, ({
|
||||
{sfLoanID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
}))
|
||||
|
||||
/** This transaction type converts into confidential MPT balance. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT, 85, ConfidentialMPTConvert,
|
||||
Delegation::delegable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeREQUIRED},
|
||||
{sfHolderEncryptionKey, soeOPTIONAL},
|
||||
{sfHolderEncryptedAmount, soeREQUIRED},
|
||||
{sfIssuerEncryptedAmount, soeREQUIRED},
|
||||
{sfAuditorEncryptedAmount, soeOPTIONAL},
|
||||
{sfBlindingFactor, soeREQUIRED},
|
||||
{sfZKProof, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type merges MPT inbox. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_MERGE_INBOX, 86, ConfidentialMPTMergeInbox,
|
||||
Delegation::delegable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction type converts back into public MPT balance. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_CONVERT_BACK, 87, ConfidentialMPTConvertBack,
|
||||
Delegation::delegable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeREQUIRED},
|
||||
{sfHolderEncryptedAmount, soeREQUIRED},
|
||||
{sfIssuerEncryptedAmount, soeREQUIRED},
|
||||
{sfAuditorEncryptedAmount, soeOPTIONAL},
|
||||
{sfBlindingFactor, soeREQUIRED},
|
||||
{sfZKProof, soeREQUIRED},
|
||||
{sfBalanceCommitment, soeREQUIRED},
|
||||
}))
|
||||
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTSend.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_SEND, 88, ConfidentialMPTSend,
|
||||
Delegation::delegable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfSenderEncryptedAmount, soeREQUIRED},
|
||||
{sfDestinationEncryptedAmount, soeREQUIRED},
|
||||
{sfIssuerEncryptedAmount, soeREQUIRED},
|
||||
{sfAuditorEncryptedAmount, soeOPTIONAL},
|
||||
{sfZKProof, soeREQUIRED},
|
||||
{sfAmountCommitment, soeREQUIRED},
|
||||
{sfBalanceCommitment, soeREQUIRED},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
#if TRANSACTION_INCLUDE
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTClawback.h>
|
||||
#endif
|
||||
TRANSACTION(ttCONFIDENTIAL_MPT_CLAWBACK, 89, ConfidentialMPTClawback,
|
||||
Delegation::delegable,
|
||||
featureConfidentialTransfer,
|
||||
noPriv,
|
||||
({
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfMPTAmount, soeREQUIRED},
|
||||
{sfZKProof, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
For details, see: https://xrpl.org/amendments.html
|
||||
|
||||
@@ -366,8 +366,7 @@ using InvariantChecks = std::tuple<
|
||||
ValidPseudoAccounts,
|
||||
ValidLoanBroker,
|
||||
ValidLoan,
|
||||
ValidVault,
|
||||
ValidConfidentialMPToken>;
|
||||
ValidVault>;
|
||||
|
||||
/**
|
||||
* @brief get a tuple of all invariant checks
|
||||
|
||||
@@ -28,47 +28,4 @@ public:
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariants: Confidential MPToken consistency
|
||||
*
|
||||
* - Convert/ConvertBack symmetry:
|
||||
* Regular MPToken balance change (±X) == COA (Confidential Outstanding Amount) change (∓X)
|
||||
* - Cannot delete MPToken with non-zero confidential state:
|
||||
* Cannot delete if sfIssuerEncryptedBalance exists
|
||||
* Cannot delete if sfConfidentialBalanceInbox and sfConfidentialBalanceSpending exist
|
||||
* - Privacy flag consistency:
|
||||
* MPToken can only have encrypted fields if lsfMPTCanConfidentialAmount is set on
|
||||
* issuance.
|
||||
* - Encrypted field existence consistency:
|
||||
* If sfConfidentialBalanceSpending/sfConfidentialBalanceInbox exists, then
|
||||
* sfIssuerEncryptedBalance must also exist (and vice versa).
|
||||
* - COA <= OutstandingAmount:
|
||||
* Confidential outstanding balance cannot exceed total outstanding.
|
||||
* - Verifies sfConfidentialBalanceVersion is changed whenever sfConfidentialBalanceSpending is
|
||||
* modified on an MPToken.
|
||||
*/
|
||||
class ValidConfidentialMPToken
|
||||
{
|
||||
struct Changes
|
||||
{
|
||||
std::int64_t mptAmountDelta = 0;
|
||||
std::int64_t coaDelta = 0;
|
||||
std::int64_t outstandingDelta = 0;
|
||||
SLE::const_pointer issuance;
|
||||
bool deletedWithEncrypted = false;
|
||||
bool badConsistency = false;
|
||||
bool badCOA = false;
|
||||
bool requiresPrivacyFlag = false;
|
||||
bool badVersion = false;
|
||||
};
|
||||
std::map<uint192, Changes> changes_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(bool, std::shared_ptr<SLE const> const&, std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(STTx const&, TER const, XRPAmount const, ReadView const&, beast::Journal const&);
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
29
include/xrpl/tx/transactors/credentials/CredentialAccept.h
Normal file
29
include/xrpl/tx/transactors/credentials/CredentialAccept.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CredentialAccept : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CredentialAccept(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
29
include/xrpl/tx/transactors/credentials/CredentialCreate.h
Normal file
29
include/xrpl/tx/transactors/credentials/CredentialCreate.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CredentialCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CredentialCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
29
include/xrpl/tx/transactors/credentials/CredentialDelete.h
Normal file
29
include/xrpl/tx/transactors/credentials/CredentialDelete.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CredentialDelete : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CredentialDelete(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,77 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class CredentialCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CredentialCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class CredentialDelete : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CredentialDelete(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class CredentialAccept : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit CredentialAccept(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -4,24 +4,6 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class DIDSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit DIDSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class DIDDelete : public Transactor
|
||||
{
|
||||
public:
|
||||
23
include/xrpl/tx/transactors/did/DIDSet.h
Normal file
23
include/xrpl/tx/transactors/did/DIDSet.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class DIDSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit DIDSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class EscrowCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit EscrowCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class EscrowFinish : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit EscrowFinish(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflightSigValidated(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class EscrowCancel : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit EscrowCancel(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
26
include/xrpl/tx/transactors/escrow/EscrowCancel.h
Normal file
26
include/xrpl/tx/transactors/escrow/EscrowCancel.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class EscrowCancel : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit EscrowCancel(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
29
include/xrpl/tx/transactors/escrow/EscrowCreate.h
Normal file
29
include/xrpl/tx/transactors/escrow/EscrowCreate.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class EscrowCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit EscrowCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
35
include/xrpl/tx/transactors/escrow/EscrowFinish.h
Normal file
35
include/xrpl/tx/transactors/escrow/EscrowFinish.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class EscrowFinish : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit EscrowFinish(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflightSigValidated(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class PayChanCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit PayChanCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
using PaymentChannelCreate = PayChanCreate;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class PayChanFund : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit PayChanFund(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
using PaymentChannelFund = PayChanFund;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class PayChanClaim : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit PayChanClaim(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
using PaymentChannelClaim = PayChanClaim;
|
||||
|
||||
} // namespace xrpl
|
||||
34
include/xrpl/tx/transactors/payment_channel/PayChanClaim.h
Normal file
34
include/xrpl/tx/transactors/payment_channel/PayChanClaim.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class PayChanClaim : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit PayChanClaim(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
checkExtraFeatures(PreflightContext const& ctx);
|
||||
|
||||
static std::uint32_t
|
||||
getFlagsMask(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
using PaymentChannelClaim = PayChanClaim;
|
||||
|
||||
} // namespace xrpl
|
||||
31
include/xrpl/tx/transactors/payment_channel/PayChanCreate.h
Normal file
31
include/xrpl/tx/transactors/payment_channel/PayChanCreate.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class PayChanCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit PayChanCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
using PaymentChannelCreate = PayChanCreate;
|
||||
|
||||
} // namespace xrpl
|
||||
28
include/xrpl/tx/transactors/payment_channel/PayChanFund.h
Normal file
28
include/xrpl/tx/transactors/payment_channel/PayChanFund.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
class PayChanFund : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
explicit PayChanFund(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
using PaymentChannelFund = PayChanFund;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -49,11 +49,6 @@ public:
|
||||
ttLOAN_DELETE,
|
||||
ttLOAN_MANAGE,
|
||||
ttLOAN_PAY,
|
||||
ttCONFIDENTIAL_MPT_SEND,
|
||||
ttCONFIDENTIAL_MPT_CONVERT,
|
||||
ttCONFIDENTIAL_MPT_CONVERT_BACK,
|
||||
ttCONFIDENTIAL_MPT_MERGE_INBOX,
|
||||
ttCONFIDENTIAL_MPT_CLAWBACK,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Allows an MPT issuer to clawback confidential balances from a holder.
|
||||
*
|
||||
* This transaction enables the issuer of an MPToken Issuance (with clawback
|
||||
* enabled) to reclaim confidential tokens from a holder's account. Unlike
|
||||
* regular clawback, the issuer cannot see the holder's balance directly.
|
||||
* Instead, the issuer must provide a zero-knowledge proof that demonstrates
|
||||
* they know the exact encrypted balance amount.
|
||||
*
|
||||
* @par Cryptographic Operations:
|
||||
* - **Equality Proof Verification**: Verifies that the issuer's revealed
|
||||
* amount matches the holder's encrypted balance using the issuer's
|
||||
* ElGamal private key.
|
||||
*
|
||||
* @see ConfidentialMPTSend, ConfidentialMPTConvert
|
||||
*/
|
||||
class ConfidentialMPTClawback : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialMPTClawback(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,44 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Converts public (plaintext) MPT balance to confidential (encrypted)
|
||||
* balance.
|
||||
*
|
||||
* This transaction allows a token holder to convert their publicly visible
|
||||
* MPToken balance into an encrypted confidential balance. Once converted,
|
||||
* the balance can only be spent using ConfidentialMPTSend transactions and
|
||||
* remains hidden from public view on the ledger.
|
||||
*
|
||||
* @par Cryptographic Operations:
|
||||
* - **Schnorr Proof Verification**: When registering a new ElGamal public key,
|
||||
* verifies proof of knowledge of the corresponding private key.
|
||||
* - **Revealed Amount Verification**: Verifies that the provided encrypted
|
||||
* amounts (for holder, issuer, and optionally auditor) all encrypt the
|
||||
* same plaintext amount using the provided blinding factor.
|
||||
*
|
||||
* @see ConfidentialMPTConvertBack, ConfidentialMPTSend
|
||||
*/
|
||||
class ConfidentialMPTConvert : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialMPTConvert(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Converts confidential (encrypted) MPT balance back to public
|
||||
* (plaintext) balance.
|
||||
*
|
||||
* This transaction allows a token holder to convert their encrypted
|
||||
* confidential balance back into a publicly visible MPToken balance. The
|
||||
* holder must prove they have sufficient confidential balance without
|
||||
* revealing the actual balance amount.
|
||||
*
|
||||
* @par Cryptographic Operations:
|
||||
* - **Revealed Amount Verification**: Verifies that the provided encrypted
|
||||
* amounts correctly encrypt the conversion amount.
|
||||
* - **Pedersen Linkage Proof**: Verifies that the provided balance commitment
|
||||
* correctly links to the holder's encrypted spending balance.
|
||||
* - **Bulletproof Range Proof**: Verifies that the remaining balance (after
|
||||
* conversion) is non-negative, ensuring the holder has sufficient funds.
|
||||
*
|
||||
* @see ConfidentialMPTConvert, ConfidentialMPTSend
|
||||
*/
|
||||
class ConfidentialMPTConvertBack : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialMPTConvertBack(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Merges the confidential inbox balance into the spending balance.
|
||||
*
|
||||
* In the confidential transfer system, incoming funds are deposited into an
|
||||
* "inbox" balance that the recipient cannot immediately spend. This prevents
|
||||
* front-running attacks where an attacker could invalidate a pending
|
||||
* transaction by sending funds to the sender. This transaction merges the
|
||||
* inbox into the spending balance, making those funds available for spending.
|
||||
*
|
||||
* @par Cryptographic Operations:
|
||||
* - **Homomorphic Addition**: Adds the encrypted inbox balance to the
|
||||
* encrypted spending balance using ElGamal homomorphic properties.
|
||||
* - **Zero Encryption**: Resets the inbox to an encryption of zero.
|
||||
*
|
||||
* @note This transaction requires no zero-knowledge proofs because it only
|
||||
* combines encrypted values that the holder already owns. The
|
||||
* homomorphic properties of ElGamal encryption ensure correctness.
|
||||
*
|
||||
* @see ConfidentialMPTSend, ConfidentialMPTConvert
|
||||
*/
|
||||
class ConfidentialMPTMergeInbox : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialMPTMergeInbox(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,52 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/tx/Transactor.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/**
|
||||
* @brief Transfers confidential MPT tokens between holders privately.
|
||||
*
|
||||
* This transaction enables private token transfers where the transfer amount
|
||||
* is hidden from public view. Both sender and recipient must have initialized
|
||||
* confidential balances. The transaction provides encrypted amounts for all
|
||||
* parties (sender, destination, issuer, and optionally auditor) along with
|
||||
* zero-knowledge proofs that verify correctness without revealing the amount.
|
||||
*
|
||||
* @par Cryptographic Operations:
|
||||
* - **Multi-Ciphertext Equality Proof**: Verifies that all encrypted amounts
|
||||
* (sender, destination, issuer, auditor) encrypt the same plaintext value.
|
||||
* - **Amount Pedersen Linkage Proof**: Verifies that the amount commitment
|
||||
* correctly links to the sender's encrypted amount.
|
||||
* - **Balance Pedersen Linkage Proof**: Verifies that the balance commitment
|
||||
* correctly links to the sender's encrypted spending balance.
|
||||
* - **Bulletproof Range Proof**: Verifies remaining balance and
|
||||
* transfer amount are non-negative.
|
||||
*
|
||||
* @note Funds are deposited into the destination's inbox, not spending
|
||||
* balance. The recipient must call ConfidentialMPTMergeInbox to make
|
||||
* received funds spendable.
|
||||
*
|
||||
* @see ConfidentialMPTMergeInbox, ConfidentialMPTConvert,
|
||||
* ConfidentialMPTConvertBack
|
||||
*/
|
||||
class ConfidentialMPTSend : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit ConfidentialMPTSend(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -168,7 +168,7 @@ decode(void* dest, char const* src, std::size_t len)
|
||||
break;
|
||||
++in;
|
||||
c4[i] = v;
|
||||
if (++i == 4)
|
||||
if (++i; i == 4)
|
||||
{
|
||||
c3[0] = (c4[0] << 2) + ((c4[1] & 0x30) >> 4);
|
||||
c3[1] = ((c4[1] & 0xf) << 4) + ((c4[2] & 0x3c) >> 2);
|
||||
|
||||
@@ -62,7 +62,8 @@ namespace beast::detail {
|
||||
inline void
|
||||
setCurrentThreadNameImpl(std::string_view name)
|
||||
{
|
||||
pthread_setname_np(name.data());
|
||||
// The string is assumed to be null terminated
|
||||
pthread_setname_np(name.data()); // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
}
|
||||
|
||||
} // namespace beast::detail
|
||||
@@ -85,7 +86,7 @@ setCurrentThreadNameImpl(std::string_view name)
|
||||
sizeof(boundedName),
|
||||
"%.*s",
|
||||
static_cast<int>(maxThreadNameLength),
|
||||
name.data());
|
||||
name.data()); // NOLINT(bugprone-suspicious-stringview-data-usage)
|
||||
|
||||
pthread_setname_np(pthread_self(), boundedName);
|
||||
|
||||
|
||||
@@ -729,21 +729,18 @@ Reader::decodeUnicodeCodePoint(Token& token, Location& current, Location end, un
|
||||
|
||||
unsigned int surrogatePair;
|
||||
|
||||
if (*(current++) == '\\' && *(current++) == 'u')
|
||||
{
|
||||
if (decodeUnicodeEscapeSequence(token, current, end, surrogatePair))
|
||||
{
|
||||
unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
if (*current != '\\' || *(current + 1) != 'u')
|
||||
return addError(
|
||||
"expecting another \\u token to begin the second half of a "
|
||||
"unicode surrogate pair",
|
||||
"expecting another \\u token to begin the second half of a unicode surrogate pair",
|
||||
token,
|
||||
current);
|
||||
|
||||
current += 2; // skip two characters checked above
|
||||
|
||||
if (!decodeUnicodeEscapeSequence(token, current, end, surrogatePair))
|
||||
return false;
|
||||
|
||||
unicode = 0x10000 + ((unicode & 0x3FF) << 10) + (surrogatePair & 0x3FF);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -319,7 +319,7 @@ StyledWriter::writeValue(Value const& value)
|
||||
document_ += " : ";
|
||||
writeValue(childValue);
|
||||
|
||||
if (++it == members.end())
|
||||
if (++it; it == members.end())
|
||||
break;
|
||||
|
||||
document_ += ",";
|
||||
|
||||
@@ -74,8 +74,10 @@ BookDirs::const_iterator::operator++()
|
||||
XRPL_ASSERT(index_ != zero, "xrpl::BookDirs::const_iterator::operator++ : nonzero index");
|
||||
if (!cdirNext(*view_, cur_key_, sle_, entry_, index_))
|
||||
{
|
||||
if (index_ != 0 ||
|
||||
(cur_key_ = view_->succ(++cur_key_, next_quality_).value_or(zero)) == zero)
|
||||
if (index_ == 0)
|
||||
cur_key_ = view_->succ(++cur_key_, next_quality_).value_or(zero);
|
||||
|
||||
if (index_ != 0 || cur_key_ == zero)
|
||||
{
|
||||
cur_key_ = key_;
|
||||
entry_ = 0;
|
||||
@@ -84,9 +86,7 @@ BookDirs::const_iterator::operator++()
|
||||
else if (!cdirFirst(*view_, cur_key_, sle_, entry_, index_))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE(
|
||||
"xrpl::BookDirs::const_iterator::operator++ : directory is "
|
||||
"empty");
|
||||
UNREACHABLE("xrpl::BookDirs::const_iterator::operator++ : directory is empty");
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,8 +524,7 @@ accountHolds(
|
||||
// Only if auth check is needed, as it needs to do an additional read
|
||||
// operation. Note featureSingleAssetVault will affect error codes.
|
||||
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
||||
(view.rules().enabled(featureSingleAssetVault) ||
|
||||
view.rules().enabled(featureConfidentialTransfer)))
|
||||
view.rules().enabled(featureSingleAssetVault))
|
||||
{
|
||||
if (auto const err = requireAuth(view, mptIssue, account, AuthType::StrongAuth);
|
||||
!isTesSuccess(err))
|
||||
|
||||
@@ -1,810 +0,0 @@
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
|
||||
#include <boost/endian/conversion.hpp>
|
||||
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
static constexpr std::uint32_t defaultVersion = 0;
|
||||
|
||||
void
|
||||
addCommonZKPFields(
|
||||
Serializer& s,
|
||||
std::uint16_t txType,
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence)
|
||||
{
|
||||
// TxCommonHash = hash(TxType || Account || IssuanceID || SequenceOrTicket)
|
||||
s.add16(txType);
|
||||
s.addBitString(account);
|
||||
s.addBitString(issuanceID);
|
||||
s.add32(sequence);
|
||||
}
|
||||
|
||||
uint256
|
||||
getSendContextHash(
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence,
|
||||
AccountID const& destination,
|
||||
std::uint32_t version)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_SEND, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(destination);
|
||||
s.addInteger(version);
|
||||
|
||||
return s.getSHA512Half();
|
||||
}
|
||||
|
||||
uint256
|
||||
getClawbackContextHash(
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence,
|
||||
AccountID const& holder)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CLAWBACK, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(holder);
|
||||
s.addInteger(defaultVersion);
|
||||
|
||||
return s.getSHA512Half();
|
||||
}
|
||||
|
||||
uint256
|
||||
getConvertContextHash(AccountID const& account, uint192 const& issuanceID, std::uint32_t sequence)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(account);
|
||||
s.addInteger(defaultVersion);
|
||||
|
||||
return s.getSHA512Half();
|
||||
}
|
||||
|
||||
uint256
|
||||
getConvertBackContextHash(
|
||||
AccountID const& account,
|
||||
uint192 const& issuanceID,
|
||||
std::uint32_t sequence,
|
||||
std::uint32_t version)
|
||||
{
|
||||
Serializer s;
|
||||
addCommonZKPFields(s, ttCONFIDENTIAL_MPT_CONVERT_BACK, account, issuanceID, sequence);
|
||||
|
||||
// TxSpecific = identity || freshness
|
||||
s.addBitString(account);
|
||||
s.addInteger(version);
|
||||
|
||||
return s.getSHA512Half();
|
||||
}
|
||||
|
||||
std::optional<EcPair>
|
||||
makeEcPair(Slice const& buffer)
|
||||
{
|
||||
if (buffer.length() != 2 * ecGamalEncryptedLength)
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
auto parsePubKey = [](Slice const& slice, secp256k1_pubkey& out) {
|
||||
return secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(),
|
||||
&out,
|
||||
reinterpret_cast<unsigned char const*>(slice.data()),
|
||||
slice.length());
|
||||
};
|
||||
|
||||
Slice s1{buffer.data(), ecGamalEncryptedLength};
|
||||
Slice s2{buffer.data() + ecGamalEncryptedLength, ecGamalEncryptedLength};
|
||||
|
||||
EcPair pair;
|
||||
if (parsePubKey(s1, pair.c1) != 1 || parsePubKey(s2, pair.c2) != 1)
|
||||
return std::nullopt;
|
||||
|
||||
return pair;
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
serializeEcPair(EcPair const& pair)
|
||||
{
|
||||
auto serializePubKey = [](secp256k1_pubkey const& pub, unsigned char* out) {
|
||||
size_t outLen = ecGamalEncryptedLength; // 33 bytes
|
||||
int const ret = secp256k1_ec_pubkey_serialize(
|
||||
secp256k1Context(), out, &outLen, &pub, SECP256K1_EC_COMPRESSED);
|
||||
return ret == 1 && outLen == ecGamalEncryptedLength;
|
||||
};
|
||||
|
||||
Buffer buffer(ecGamalEncryptedTotalLength);
|
||||
unsigned char* ptr = buffer.data();
|
||||
bool const res1 = serializePubKey(pair.c1, ptr);
|
||||
bool const res2 = serializePubKey(pair.c2, ptr + ecGamalEncryptedLength);
|
||||
|
||||
if (!res1 || !res2)
|
||||
return std::nullopt;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool
|
||||
isValidCiphertext(Slice const& buffer)
|
||||
{
|
||||
return makeEcPair(buffer).has_value();
|
||||
}
|
||||
|
||||
bool
|
||||
isValidCompressedECPoint(Slice const& buffer)
|
||||
{
|
||||
if (buffer.size() != compressedECPointLength)
|
||||
return false;
|
||||
|
||||
// Compressed EC points must start with 0x02 or 0x03
|
||||
if (buffer[0] != ecCompressedPrefixEvenY && buffer[0] != ecCompressedPrefixOddY)
|
||||
return false;
|
||||
|
||||
secp256k1_pubkey point;
|
||||
return secp256k1_ec_pubkey_parse(secp256k1Context(), &point, buffer.data(), buffer.size()) == 1;
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
homomorphicAdd(Slice const& a, Slice const& b)
|
||||
{
|
||||
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
|
||||
return std::nullopt;
|
||||
|
||||
auto const pairA = makeEcPair(a);
|
||||
auto const pairB = makeEcPair(b);
|
||||
|
||||
if (!pairA || !pairB)
|
||||
return std::nullopt;
|
||||
|
||||
EcPair sum;
|
||||
if (auto res = secp256k1_elgamal_add(
|
||||
secp256k1Context(), &sum.c1, &sum.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return serializeEcPair(sum);
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
homomorphicSubtract(Slice const& a, Slice const& b)
|
||||
{
|
||||
if (a.length() != ecGamalEncryptedTotalLength || b.length() != ecGamalEncryptedTotalLength)
|
||||
return std::nullopt;
|
||||
|
||||
auto const pairA = makeEcPair(a);
|
||||
auto const pairB = makeEcPair(b);
|
||||
|
||||
if (!pairA || !pairB)
|
||||
return std::nullopt;
|
||||
|
||||
EcPair diff;
|
||||
if (auto res = secp256k1_elgamal_subtract(
|
||||
secp256k1Context(), &diff.c1, &diff.c2, &pairA->c1, &pairA->c2, &pairB->c1, &pairB->c2);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return serializeEcPair(diff);
|
||||
}
|
||||
|
||||
Buffer
|
||||
generateBlindingFactor()
|
||||
{
|
||||
unsigned char blindingFactor[ecBlindingFactorLength];
|
||||
|
||||
// todo: might need to be updated using another RNG
|
||||
if (RAND_bytes(blindingFactor, ecBlindingFactorLength) != 1)
|
||||
Throw<std::runtime_error>("Failed to generate random number");
|
||||
|
||||
return Buffer(blindingFactor, ecBlindingFactorLength);
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
encryptAmount(uint64_t const amt, Slice const& pubKeySlice, Slice const& blindingFactor)
|
||||
{
|
||||
if (blindingFactor.size() != ecBlindingFactorLength)
|
||||
return std::nullopt;
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return std::nullopt;
|
||||
|
||||
EcPair pair;
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_elgamal_encrypt(
|
||||
secp256k1Context(), &pair.c1, &pair.c2, &pubKey, amt, blindingFactor.data());
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return serializeEcPair(pair);
|
||||
}
|
||||
|
||||
std::optional<Buffer>
|
||||
encryptCanonicalZeroAmount(Slice const& pubKeySlice, AccountID const& account, MPTID const& mptId)
|
||||
{
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
|
||||
EcPair pair;
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = generate_canonical_encrypted_zero(
|
||||
secp256k1Context(), &pair.c1, &pair.c2, &pubKey, account.data(), mptId.data());
|
||||
res != 1)
|
||||
{
|
||||
return std::nullopt; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return serializeEcPair(pair);
|
||||
}
|
||||
|
||||
TER
|
||||
verifySchnorrProof(Slice const& pubKeySlice, Slice const& proofSlice, uint256 const& contextHash)
|
||||
{
|
||||
if (proofSlice.size() != ecSchnorrProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_mpt_pok_sk_verify(
|
||||
secp256k1Context(), proofSlice.data(), &pubKey, contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyElGamalEncryption(
|
||||
uint64_t const amount,
|
||||
Slice const& blindingFactor,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext)
|
||||
{
|
||||
if (ciphertext.size() != ecGamalEncryptedTotalLength ||
|
||||
blindingFactor.size() != ecBlindingFactorLength || pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
auto const pair = makeEcPair(ciphertext);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto res = secp256k1_elgamal_verify_encryption(
|
||||
secp256k1Context(), &pair->c1, &pair->c2, &pubKey, amount, blindingFactor.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyRevealedAmount(
|
||||
uint64_t const amount,
|
||||
Slice const& blindingFactor,
|
||||
ConfidentialRecipient const& holder,
|
||||
ConfidentialRecipient const& issuer,
|
||||
std::optional<ConfidentialRecipient> const& auditor)
|
||||
{
|
||||
if (auto const res = verifyElGamalEncryption(
|
||||
amount, blindingFactor, holder.publicKey, holder.encryptedAmount);
|
||||
!isTesSuccess(res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (auto const res = verifyElGamalEncryption(
|
||||
amount, blindingFactor, issuer.publicKey, issuer.encryptedAmount);
|
||||
!isTesSuccess(res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
if (auditor)
|
||||
{
|
||||
if (auto const res = verifyElGamalEncryption(
|
||||
amount, blindingFactor, auditor->publicKey, auditor->encryptedAmount);
|
||||
!isTesSuccess(res))
|
||||
{
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyMultiCiphertextEqualityProof(
|
||||
Slice const& proof,
|
||||
std::vector<ConfidentialRecipient> const& recipients,
|
||||
std::size_t const nRecipients,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (recipients.size() != nRecipients)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (proof.size() != secp256k1_mpt_proof_equality_shared_r_size(nRecipients))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ctx = secp256k1Context();
|
||||
|
||||
secp256k1_pubkey c1;
|
||||
std::vector<secp256k1_pubkey> c2_vec(nRecipients);
|
||||
std::vector<secp256k1_pubkey> pk_vec(nRecipients);
|
||||
|
||||
for (size_t i = 0; i < nRecipients; ++i)
|
||||
{
|
||||
auto const& recipient = recipients[i];
|
||||
|
||||
if (recipient.encryptedAmount.size() != ecGamalEncryptedTotalLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (recipient.publicKey.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Parse Shared C1 from the first recipient only
|
||||
if (i == 0)
|
||||
{
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &c1, recipient.encryptedAmount.data(), ecGamalEncryptedLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// All C1 bytes must be the same
|
||||
if (std::memcmp(
|
||||
recipient.encryptedAmount.data(),
|
||||
recipients[0].encryptedAmount.data(),
|
||||
ecGamalEncryptedLength) != 0)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx,
|
||||
&c2_vec[i],
|
||||
recipient.encryptedAmount.data() + ecGamalEncryptedLength,
|
||||
ecGamalEncryptedLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pk_vec[i], recipient.publicKey.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_mpt_verify_equality_shared_r(
|
||||
ctx, proof.data(), nRecipients, &c1, c2_vec.data(), pk_vec.data(), contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyClawbackEqualityProof(
|
||||
uint64_t const amount,
|
||||
Slice const& proof,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& ciphertext,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (ciphertext.size() != ecGamalEncryptedTotalLength || pubKeySlice.size() != ecPubKeyLength ||
|
||||
proof.size() != ecEqualityProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const pair = makeEcPair(ciphertext);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Note: c2, c1 order - the proof is generated with c2 first (the encrypted
|
||||
// message component) because the equality proof structure expects the
|
||||
// message-containing term before the blinding term.
|
||||
if (auto res = secp256k1_equality_plaintext_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pubKey,
|
||||
&pair->c2,
|
||||
&pair->c1,
|
||||
amount,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
checkEncryptedAmountFormat(STObject const& object)
|
||||
{
|
||||
// Current usage of this function is only for ConfidentialMPTConvert and
|
||||
// ConfidentialMPTConvertBack transactions, which already enforce that these fields
|
||||
// are present.
|
||||
if (!object.isFieldPresent(sfHolderEncryptedAmount) ||
|
||||
!object.isFieldPresent(sfIssuerEncryptedAmount))
|
||||
return temMALFORMED; // LCOV_EXCL_LINE
|
||||
|
||||
if (object[sfHolderEncryptedAmount].length() != ecGamalEncryptedTotalLength ||
|
||||
object[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
bool const hasAuditor = object.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
if (hasAuditor && object[sfAuditorEncryptedAmount].length() != ecGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (!isValidCiphertext(object[sfHolderEncryptedAmount]) ||
|
||||
!isValidCiphertext(object[sfIssuerEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasAuditor && !isValidCiphertext(object[sfAuditorEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyAmountPcmLinkage(
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (proof.length() != ecPedersenProofLength)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const pair = makeEcPair(encAmt);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pcmSlice.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
secp256k1_pubkey pcm;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pcm, pcmSlice.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res = secp256k1_elgamal_pedersen_link_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pair->c1,
|
||||
&pair->c2,
|
||||
&pubKey,
|
||||
&pcm,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyBalancePcmLinkage(
|
||||
Slice const& proof,
|
||||
Slice const& encAmt,
|
||||
Slice const& pubKeySlice,
|
||||
Slice const& pcmSlice,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
if (proof.length() != ecPedersenProofLength)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const pair = makeEcPair(encAmt);
|
||||
if (!pair)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pubKeySlice.size() != ecPubKeyLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (pcmSlice.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
secp256k1_pubkey pubKey;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pubKey, pubKeySlice.data(), ecPubKeyLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
secp256k1_pubkey pcm;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
secp256k1Context(), &pcm, pcmSlice.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Note: c2, c1 order - the linkage proof expects the message-containing
|
||||
// component (c2 = m*G + r*Pk) before the blinding component (c1 = r*G).
|
||||
if (auto res = secp256k1_elgamal_pedersen_link_verify(
|
||||
secp256k1Context(),
|
||||
proof.data(),
|
||||
&pubKey,
|
||||
&pair->c2,
|
||||
&pair->c1,
|
||||
&pcm,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifyAggregatedBulletproof(
|
||||
Slice const& proof,
|
||||
std::vector<Slice> const& compressedCommitments,
|
||||
uint256 const& contextHash)
|
||||
{
|
||||
// 1. Validate input lengths
|
||||
// This function could support any power-of-2 m, but current usage only requires m=1 or m=2
|
||||
std::size_t const m = compressedCommitments.size();
|
||||
if (m != 1 && m != 2)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
std::size_t const expectedProofLen =
|
||||
(m == 1) ? ecSingleBulletproofLength : ecDoubleBulletproofLength;
|
||||
if (proof.size() != expectedProofLen)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// 2. Prepare Pedersen Commitments, parse from compressed format
|
||||
auto const ctx = secp256k1Context();
|
||||
std::vector<secp256k1_pubkey> commitments(m);
|
||||
for (size_t i = 0; i < m; ++i)
|
||||
{
|
||||
// Sanity check length
|
||||
if (compressedCommitments[i].size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &commitments[i], compressedCommitments[i].data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Prepare Generator Vectors (G_vec, H_vec)
|
||||
// The range proof requires vectors of size 64 * m
|
||||
std::size_t const n = 64 * m;
|
||||
std::vector<secp256k1_pubkey> G_vec(n);
|
||||
std::vector<secp256k1_pubkey> H_vec(n);
|
||||
|
||||
// Retrieve deterministic generators "G" and "H"
|
||||
if (auto res =
|
||||
secp256k1_mpt_get_generator_vector(ctx, G_vec.data(), n, (unsigned char const*)"G", 1);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (auto res =
|
||||
secp256k1_mpt_get_generator_vector(ctx, H_vec.data(), n, (unsigned char const*)"H", 1);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// 4. Prepare Base Generator (pk_base / H)
|
||||
secp256k1_pubkey pk_base;
|
||||
if (auto res = secp256k1_mpt_get_h_generator(ctx, &pk_base); res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// 5. Verify the Proof
|
||||
if (auto res = secp256k1_bulletproof_verify_agg(
|
||||
ctx,
|
||||
G_vec.data(),
|
||||
H_vec.data(),
|
||||
reinterpret_cast<unsigned char const*>(proof.data()),
|
||||
proof.size(),
|
||||
commitments.data(),
|
||||
m,
|
||||
&pk_base,
|
||||
contextHash.data());
|
||||
res != 1)
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
computeSendRemainder(Slice const& balanceCommitment, Slice const& amountCommitment, Buffer& out)
|
||||
{
|
||||
if (balanceCommitment.size() != ecPedersenCommitmentLength ||
|
||||
amountCommitment.size() != ecPedersenCommitmentLength)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const ctx = secp256k1Context();
|
||||
|
||||
secp256k1_pubkey pcBalance;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, balanceCommitment.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
secp256k1_pubkey pcAmount;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcAmount, amountCommitment.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
// Negate PC_amount point to get -PC_amount
|
||||
if (auto res = secp256k1_ec_pubkey_negate(ctx, &pcAmount); res != 1)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
// Compute pcRem = pcBalance + (-pcAmount)
|
||||
secp256k1_pubkey const* summands[2] = {&pcBalance, &pcAmount};
|
||||
secp256k1_pubkey pcRem;
|
||||
if (auto res = secp256k1_ec_pubkey_combine(ctx, &pcRem, summands, 2); res != 1)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
// Serialize result to compressed format
|
||||
out.alloc(ecPedersenCommitmentLength);
|
||||
size_t outLen = ecPedersenCommitmentLength;
|
||||
if (auto res = secp256k1_ec_pubkey_serialize(
|
||||
ctx, out.data(), &outLen, &pcRem, SECP256K1_EC_COMPRESSED);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
computeConvertBackRemainder(Slice const& commitment, uint64_t amount, Buffer& out)
|
||||
{
|
||||
if (commitment.size() != ecPedersenCommitmentLength || amount == 0)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ctx = secp256k1Context();
|
||||
|
||||
// Parse commitment from compressed format
|
||||
secp256k1_pubkey pcBalance;
|
||||
if (auto res = secp256k1_ec_pubkey_parse(
|
||||
ctx, &pcBalance, commitment.data(), ecPedersenCommitmentLength);
|
||||
res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Convert amount to 32-byte big-endian scalar
|
||||
unsigned char mScalar[32] = {0};
|
||||
uint64_t amountBigEndian = boost::endian::native_to_big(amount);
|
||||
std::memcpy(&mScalar[24], &amountBigEndian, sizeof(amountBigEndian));
|
||||
|
||||
// Compute mG = amount * G
|
||||
secp256k1_pubkey mG;
|
||||
if (auto res = secp256k1_ec_pubkey_create(ctx, &mG, mScalar); res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Negate mG to get -mG
|
||||
if (auto res = secp256k1_ec_pubkey_negate(ctx, &mG); res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Compute pcRem = pcBalance + (-mG)
|
||||
secp256k1_pubkey const* summands[2] = {&pcBalance, &mG};
|
||||
secp256k1_pubkey pcRem;
|
||||
if (auto res = secp256k1_ec_pubkey_combine(ctx, &pcRem, summands, 2); res != 1)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Serialize result to compressed format
|
||||
out.alloc(ecPedersenCommitmentLength);
|
||||
size_t outLen = ecPedersenCommitmentLength;
|
||||
if (auto res = secp256k1_ec_pubkey_serialize(
|
||||
ctx, out.data(), &outLen, &pcRem, SECP256K1_EC_COMPRESSED);
|
||||
res != 1 || outLen != ecPedersenCommitmentLength)
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
} // namespace xrpl
|
||||
@@ -86,7 +86,7 @@ Permission::getPermissionName(std::uint32_t const value) const
|
||||
{
|
||||
auto const permissionValue = static_cast<GranularPermissionType>(value);
|
||||
if (auto const granular = getGranularName(permissionValue))
|
||||
return *granular;
|
||||
return granular;
|
||||
|
||||
// not a granular permission, check if it maps to a transaction type
|
||||
auto const txType = permissionToTxType(value);
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/strHex.h>
|
||||
#include <xrpl/protocol/KeyType.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/protocol/detail/secp256k1.h>
|
||||
@@ -207,7 +206,7 @@ publicKeyType(Slice const& slice)
|
||||
if (slice[0] == 0xED)
|
||||
return KeyType::ed25519;
|
||||
|
||||
if (slice[0] == ecCompressedPrefixEvenY || slice[0] == ecCompressedPrefixOddY)
|
||||
if (slice[0] == 0x02 || slice[0] == 0x03)
|
||||
return KeyType::secp256k1;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ STBase::STBase(SField const& n) : fName(&n)
|
||||
STBase&
|
||||
STBase::operator=(STBase const& t)
|
||||
{
|
||||
if (this == &t)
|
||||
return *this;
|
||||
|
||||
if (!fName->isUseful())
|
||||
fName = t.fName;
|
||||
return *this;
|
||||
|
||||
@@ -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(tecBAD_PROOF, "Proof cannot be verified"),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -199,7 +198,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_CIPHERTEXT, "Malformed: Invalid ciphertext."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -39,6 +39,9 @@ Consumer::~Consumer()
|
||||
Consumer&
|
||||
Consumer::operator=(Consumer const& other)
|
||||
{
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
// remove old ref
|
||||
if (m_logic && m_entry)
|
||||
m_logic->release(*m_entry);
|
||||
|
||||
@@ -488,8 +488,9 @@ AccountRootsDeletedClean::finalize(
|
||||
return false;
|
||||
}
|
||||
// Simple types
|
||||
for (auto const& [keyletfunc, _, __] : directAccountKeylets)
|
||||
for (auto const& [keyletfunc, _1, _2] : directAccountKeylets)
|
||||
{
|
||||
// TODO: use '_' for both unused variables above once we are in C++26
|
||||
if (objectExists(std::invoke(keyletfunc, accountID)) && enforce)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -189,208 +189,4 @@ ValidMPTIssuance::finalize(
|
||||
mptokensDeleted_ == 0;
|
||||
}
|
||||
|
||||
void
|
||||
ValidConfidentialMPToken::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// Helper to get MPToken Issuance ID safely
|
||||
auto const getMptID = [](std::shared_ptr<SLE const> const& sle) -> uint192 {
|
||||
if (!sle)
|
||||
return beast::zero;
|
||||
if (sle->getType() == ltMPTOKEN)
|
||||
return sle->getFieldH192(sfMPTokenIssuanceID);
|
||||
if (sle->getType() == ltMPTOKEN_ISSUANCE)
|
||||
return makeMptID(sle->getFieldU32(sfSequence), sle->getAccountID(sfIssuer));
|
||||
return beast::zero;
|
||||
};
|
||||
|
||||
if (before && before->getType() == ltMPTOKEN)
|
||||
{
|
||||
uint192 const id = getMptID(before);
|
||||
changes_[id].mptAmountDelta -= before->getFieldU64(sfMPTAmount);
|
||||
|
||||
// Cannot delete MPToken with non-zero confidential state or non-zero public amount
|
||||
if (isDelete)
|
||||
{
|
||||
bool const hasPublicBalance = before->getFieldU64(sfMPTAmount) > 0;
|
||||
bool const hasEncryptedFields = before->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
before->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
before->isFieldPresent(sfIssuerEncryptedBalance);
|
||||
|
||||
if (hasPublicBalance || hasEncryptedFields)
|
||||
changes_[id].deletedWithEncrypted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (after && after->getType() == ltMPTOKEN)
|
||||
{
|
||||
uint192 const id = getMptID(after);
|
||||
changes_[id].mptAmountDelta += after->getFieldU64(sfMPTAmount);
|
||||
|
||||
// Encrypted field existence consistency
|
||||
bool const hasIssuerBalance = after->isFieldPresent(sfIssuerEncryptedBalance);
|
||||
bool const hasHolderInbox = after->isFieldPresent(sfConfidentialBalanceInbox);
|
||||
bool const hasHolderSpending = after->isFieldPresent(sfConfidentialBalanceSpending);
|
||||
|
||||
bool const hasAnyHolder = hasHolderInbox || hasHolderSpending;
|
||||
|
||||
if (hasAnyHolder != hasIssuerBalance)
|
||||
{
|
||||
changes_[id].badConsistency = true;
|
||||
}
|
||||
|
||||
// Privacy flag consistency
|
||||
bool const hasEncrypted = hasAnyHolder || hasIssuerBalance;
|
||||
if (hasEncrypted)
|
||||
changes_[id].requiresPrivacyFlag = true;
|
||||
}
|
||||
|
||||
if (before && before->getType() == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
uint192 const id = getMptID(before);
|
||||
if (before->isFieldPresent(sfConfidentialOutstandingAmount))
|
||||
changes_[id].coaDelta -= before->getFieldU64(sfConfidentialOutstandingAmount);
|
||||
changes_[id].outstandingDelta -= before->getFieldU64(sfOutstandingAmount);
|
||||
}
|
||||
|
||||
if (after && after->getType() == ltMPTOKEN_ISSUANCE)
|
||||
{
|
||||
uint192 const id = getMptID(after);
|
||||
auto& change = changes_[id];
|
||||
|
||||
bool const hasCOA = after->isFieldPresent(sfConfidentialOutstandingAmount);
|
||||
std::uint64_t const coa = (*after)[~sfConfidentialOutstandingAmount].value_or(0);
|
||||
std::uint64_t const oa = after->getFieldU64(sfOutstandingAmount);
|
||||
|
||||
if (hasCOA)
|
||||
change.coaDelta += coa;
|
||||
|
||||
change.outstandingDelta += oa;
|
||||
change.issuance = after;
|
||||
|
||||
// COA <= OutstandingAmount
|
||||
if (coa > oa)
|
||||
change.badCOA = true;
|
||||
}
|
||||
|
||||
if (before && after && before->getType() == ltMPTOKEN && after->getType() == ltMPTOKEN)
|
||||
{
|
||||
uint192 const id = getMptID(after);
|
||||
|
||||
// sfConfidentialBalanceVersion must change when spending changes
|
||||
auto const spendingBefore = (*before)[~sfConfidentialBalanceSpending];
|
||||
auto const spendingAfter = (*after)[~sfConfidentialBalanceSpending];
|
||||
auto const versionBefore = (*before)[~sfConfidentialBalanceVersion];
|
||||
auto const versionAfter = (*after)[~sfConfidentialBalanceVersion];
|
||||
|
||||
if (spendingBefore.has_value() && spendingBefore != spendingAfter)
|
||||
{
|
||||
if (versionBefore == versionAfter)
|
||||
{
|
||||
changes_[id].badVersion = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
ValidConfidentialMPToken::finalize(
|
||||
STTx const& tx,
|
||||
TER const result,
|
||||
XRPAmount const,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (result != tesSUCCESS)
|
||||
return true;
|
||||
|
||||
for (auto const& [id, checks] : changes_)
|
||||
{
|
||||
// Find the MPTokenIssuance
|
||||
auto const issuance = [&]() -> std::shared_ptr<SLE const> {
|
||||
if (checks.issuance)
|
||||
return checks.issuance;
|
||||
return view.read(keylet::mptIssuance(id));
|
||||
}();
|
||||
|
||||
// Skip all invariance checks if issuance doesn't exist because that means the MPT has been
|
||||
// deleted
|
||||
if (!issuance)
|
||||
continue;
|
||||
|
||||
// Cannot delete MPToken with non-zero confidential state
|
||||
if (checks.deletedWithEncrypted)
|
||||
{
|
||||
if ((*issuance)[~sfConfidentialOutstandingAmount].value_or(0) > 0)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: MPToken deleted with encrypted fields while COA > 0";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Encrypted field existence consistency
|
||||
if (checks.badConsistency)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPToken encrypted field "
|
||||
"existence inconsistency";
|
||||
return false;
|
||||
}
|
||||
|
||||
// COA <= OutstandingAmount
|
||||
if (checks.badCOA)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Confidential outstanding amount "
|
||||
"exceeds total outstanding amount";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Privacy flag consistency
|
||||
if (checks.requiresPrivacyFlag)
|
||||
{
|
||||
if (!issuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPToken has encrypted "
|
||||
"fields but Issuance does not have "
|
||||
"lsfMPTCanConfidentialAmount set";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// We only enforce this when Confidential Outstanding Amount changes (Convert, ConvertBack,
|
||||
// ConfidentialClawback). This avoids falsely failing on Escrow or AMM operations that lock
|
||||
// public tokens outside of ltMPTOKEN. Convert / ConvertBack:
|
||||
// - COA and MPTAmount must have opposite deltas, which cancel each other out to zero.
|
||||
// - OA remains unchanged.
|
||||
// - Therefore, the net delta on both sides of the equation is zero.
|
||||
//
|
||||
// Clawback:
|
||||
// - MPTAmount remains unchanged.
|
||||
// - COA and OA must have identical deltas (mirrored on each side).
|
||||
// - The equation remains balanced as both sides have equal offsets.
|
||||
if (checks.coaDelta != 0)
|
||||
{
|
||||
if (checks.mptAmountDelta + checks.coaDelta != checks.outstandingDelta)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: Token conservation "
|
||||
"violation for MPT "
|
||||
<< to_string(id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (checks.badVersion)
|
||||
{
|
||||
JLOG(j.fatal())
|
||||
<< "Invariant failed: MPToken sfConfidentialBalanceVersion not updated when "
|
||||
"sfConfidentialBalanceSpending changed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/mulDiv.h>
|
||||
#include <xrpl/beast/utility/instrumentation.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
@@ -11,7 +10,7 @@
|
||||
#include <xrpl/tx/transactors/account/DeleteAccount.h>
|
||||
#include <xrpl/tx/transactors/account/SetSignerList.h>
|
||||
#include <xrpl/tx/transactors/delegate/DelegateSet.h>
|
||||
#include <xrpl/tx/transactors/did/DID.h>
|
||||
#include <xrpl/tx/transactors/did/DIDDelete.h>
|
||||
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
|
||||
#include <xrpl/tx/transactors/oracle/DeleteOracle.h>
|
||||
#include <xrpl/tx/transactors/payment/DepositPreauth.h>
|
||||
|
||||
@@ -491,7 +491,7 @@ SetAccount::doApply()
|
||||
|
||||
if (messageKey.empty())
|
||||
{
|
||||
JLOG(j_.debug()) << "set message key";
|
||||
JLOG(j_.debug()) << "clear message key";
|
||||
sle->makeFieldAbsent(sfMessageKey);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -868,7 +868,13 @@ applyClaimAttestations(
|
||||
XChainClaimAttestations curAtts{sleClaimID->getFieldArray(sfXChainClaimAttestations)};
|
||||
|
||||
auto const newAttResult = onNewAttestations(
|
||||
curAtts, view, &atts[0], &atts[0] + atts.size(), quorum, signersList, j);
|
||||
curAtts,
|
||||
view,
|
||||
&atts[0],
|
||||
&atts[0] + atts.size(), // NOLINT(bugprone-pointer-arithmetic-on-polymorphic-object)
|
||||
quorum,
|
||||
signersList,
|
||||
j);
|
||||
|
||||
// update the claim id
|
||||
sleClaimID->setFieldArray(sfXChainClaimAttestations, curAtts.toSTArray());
|
||||
@@ -1026,7 +1032,13 @@ applyCreateAccountAttestations(
|
||||
}();
|
||||
|
||||
auto const newAttResult = onNewAttestations(
|
||||
curAtts, view, &atts[0], &atts[0] + atts.size(), quorum, signersList, j);
|
||||
curAtts,
|
||||
view,
|
||||
&atts[0],
|
||||
&atts[0] + atts.size(), // NOLINT(bugprone-pointer-arithmetic-on-polymorphic-object)
|
||||
quorum,
|
||||
signersList,
|
||||
j);
|
||||
|
||||
if (!createCID)
|
||||
{
|
||||
|
||||
113
src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp
Normal file
113
src/libxrpl/tx/transactors/credentials/CredentialAccept.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/credentials/CredentialAccept.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using namespace credentials;
|
||||
|
||||
std::uint32_t
|
||||
CredentialAccept::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// 0 means "Allow any flags"
|
||||
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CredentialAccept::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.tx[sfIssuer])
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
|
||||
return temINVALID_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
auto const credType = ctx.tx[sfCredentialType];
|
||||
if (credType.empty() || (credType.size() > maxCredentialTypeLength))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: invalid size of CredentialType.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialAccept::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const subject = ctx.tx[sfAccount];
|
||||
AccountID const issuer = ctx.tx[sfIssuer];
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
|
||||
if (!ctx.view.exists(keylet::account(issuer)))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
auto const sleCred = ctx.view.read(keylet::credential(subject, issuer, credType));
|
||||
if (!sleCred)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", " << to_string(issuer)
|
||||
<< ", " << credType;
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Credential already accepted: " << to_string(subject) << ", "
|
||||
<< to_string(issuer) << ", " << credType;
|
||||
return tecDUPLICATE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
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(account_));
|
||||
auto const sleIssuer = view().peek(keylet::account(issuer));
|
||||
|
||||
if (!sleSubject || !sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (mPriorBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(account_, issuer, credType);
|
||||
auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
|
||||
|
||||
if (checkExpired(sleCred, view().header().parentCloseTime))
|
||||
{
|
||||
JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
|
||||
// delete expired credentials even if the transaction failed
|
||||
auto const err = credentials::deleteSLE(view(), sleCred, j_);
|
||||
return isTesSuccess(err) ? tecEXPIRED : err;
|
||||
}
|
||||
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
view().update(sleCred);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, -1, j_);
|
||||
adjustOwnerCount(view(), sleSubject, 1, j_);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
164
src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp
Normal file
164
src/libxrpl/tx/transactors/credentials/CredentialCreate.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/credentials/CredentialCreate.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
Credentials
|
||||
======
|
||||
|
||||
A verifiable credentials (VC
|
||||
https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
|
||||
specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
|
||||
secure and tamper-evident way to represent information about a subject, such
|
||||
as an individual, organization, or even an IoT device. These credentials are
|
||||
issued by a trusted entity and can be verified by third parties without
|
||||
directly involving the issuer at all.
|
||||
*/
|
||||
|
||||
using namespace credentials;
|
||||
|
||||
std::uint32_t
|
||||
CredentialCreate::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// 0 means "Allow any flags"
|
||||
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CredentialCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const& tx = ctx.tx;
|
||||
auto& j = ctx.j;
|
||||
|
||||
if (!tx[sfSubject])
|
||||
{
|
||||
JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const uri = tx[~sfURI];
|
||||
if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
|
||||
{
|
||||
JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const credType = tx[sfCredentialType];
|
||||
if (credType.empty() || (credType.size() > maxCredentialTypeLength))
|
||||
{
|
||||
JLOG(j.trace()) << "Malformed transaction: invalid size of CredentialType.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
auto const subject = ctx.tx[sfSubject];
|
||||
|
||||
if (!ctx.view.exists(keylet::account(subject)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Subject doesn't exist.";
|
||||
return tecNO_TARGET;
|
||||
}
|
||||
|
||||
if (ctx.view.exists(keylet::credential(subject, ctx.tx[sfAccount], credType)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Credential already exists.";
|
||||
return tecDUPLICATE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialCreate::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[sfSubject];
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(subject, account_, credType);
|
||||
|
||||
auto const sleCred = std::make_shared<SLE>(credentialKey);
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const optExp = ctx_.tx[~sfExpiration];
|
||||
if (optExp)
|
||||
{
|
||||
std::uint32_t const closeTime =
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
|
||||
if (closeTime > *optExp)
|
||||
{
|
||||
JLOG(j_.trace()) << "Malformed transaction: "
|
||||
"Expiration time is in the past.";
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
|
||||
}
|
||||
|
||||
auto const sleIssuer = view().peek(keylet::account(account_));
|
||||
if (!sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (mPriorBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
sleCred->setAccountID(sfSubject, subject);
|
||||
sleCred->setAccountID(sfIssuer, account_);
|
||||
sleCred->setFieldVL(sfCredentialType, credType);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfURI))
|
||||
sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
|
||||
|
||||
{
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), credentialKey, describeOwnerDir(account_));
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfIssuerNode, *page);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, 1, j_);
|
||||
}
|
||||
|
||||
if (subject == account_)
|
||||
{
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(subject), credentialKey, describeOwnerDir(subject));
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfSubjectNode, *page);
|
||||
view().update(view().peek(keylet::account(subject)));
|
||||
}
|
||||
|
||||
view().insert(sleCred);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
90
src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp
Normal file
90
src/libxrpl/tx/transactors/credentials/CredentialDelete.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/credentials/CredentialDelete.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
using namespace credentials;
|
||||
|
||||
std::uint32_t
|
||||
CredentialDelete::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// 0 means "Allow any flags"
|
||||
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CredentialDelete::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const subject = ctx.tx[~sfSubject];
|
||||
auto const issuer = ctx.tx[~sfIssuer];
|
||||
|
||||
if (!subject && !issuer)
|
||||
{
|
||||
// Neither field is present, the transaction is malformed.
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: "
|
||||
"No Subject or Issuer fields.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// Make sure that the passed account is valid.
|
||||
if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
|
||||
"field zeroed.";
|
||||
return temINVALID_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
auto const credType = ctx.tx[sfCredentialType];
|
||||
if (credType.empty() || (credType.size() > maxCredentialTypeLength))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: invalid size of CredentialType.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialDelete::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const account{ctx.tx[sfAccount]};
|
||||
auto const subject = ctx.tx[~sfSubject].value_or(account);
|
||||
auto const issuer = ctx.tx[~sfIssuer].value_or(account);
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
|
||||
if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
|
||||
return tecNO_ENTRY;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialDelete::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[~sfSubject].value_or(account_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
auto const sleCred = view().peek(keylet::credential(subject, issuer, credType));
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((subject != account_) && (issuer != account_) &&
|
||||
!checkExpired(sleCred, ctx_.view().header().parentCloseTime))
|
||||
{
|
||||
JLOG(j_.trace()) << "Can't delete non-expired credential.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return deleteSLE(view(), sleCred, j_);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,341 +0,0 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/credentials/Credentials.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
Credentials
|
||||
======
|
||||
|
||||
A verifiable credentials (VC
|
||||
https://en.wikipedia.org/wiki/Verifiable_credentials), as defined by the W3C
|
||||
specification (https://www.w3.org/TR/vc-data-model-2.0/), is a
|
||||
secure and tamper-evident way to represent information about a subject, such
|
||||
as an individual, organization, or even an IoT device. These credentials are
|
||||
issued by a trusted entity and can be verified by third parties without
|
||||
directly involving the issuer at all.
|
||||
*/
|
||||
|
||||
using namespace credentials;
|
||||
|
||||
// ------- CREATE --------------------------
|
||||
|
||||
std::uint32_t
|
||||
CredentialCreate::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// 0 means "Allow any flags"
|
||||
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CredentialCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const& tx = ctx.tx;
|
||||
auto& j = ctx.j;
|
||||
|
||||
if (!tx[sfSubject])
|
||||
{
|
||||
JLOG(j.trace()) << "Malformed transaction: Invalid Subject";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const uri = tx[~sfURI];
|
||||
if (uri && (uri->empty() || (uri->size() > maxCredentialURILength)))
|
||||
{
|
||||
JLOG(j.trace()) << "Malformed transaction: invalid size of URI.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
auto const credType = tx[sfCredentialType];
|
||||
if (credType.empty() || (credType.size() > maxCredentialTypeLength))
|
||||
{
|
||||
JLOG(j.trace()) << "Malformed transaction: invalid size of CredentialType.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
auto const subject = ctx.tx[sfSubject];
|
||||
|
||||
if (!ctx.view.exists(keylet::account(subject)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Subject doesn't exist.";
|
||||
return tecNO_TARGET;
|
||||
}
|
||||
|
||||
if (ctx.view.exists(keylet::credential(subject, ctx.tx[sfAccount], credType)))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Credential already exists.";
|
||||
return tecDUPLICATE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialCreate::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[sfSubject];
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(subject, account_, credType);
|
||||
|
||||
auto const sleCred = std::make_shared<SLE>(credentialKey);
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const optExp = ctx_.tx[~sfExpiration];
|
||||
if (optExp)
|
||||
{
|
||||
std::uint32_t const closeTime =
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
|
||||
if (closeTime > *optExp)
|
||||
{
|
||||
JLOG(j_.trace()) << "Malformed transaction: "
|
||||
"Expiration time is in the past.";
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
sleCred->setFieldU32(sfExpiration, ctx_.tx.getFieldU32(sfExpiration));
|
||||
}
|
||||
|
||||
auto const sleIssuer = view().peek(keylet::account(account_));
|
||||
if (!sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleIssuer->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (mPriorBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
sleCred->setAccountID(sfSubject, subject);
|
||||
sleCred->setAccountID(sfIssuer, account_);
|
||||
sleCred->setFieldVL(sfCredentialType, credType);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfURI))
|
||||
sleCred->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI));
|
||||
|
||||
{
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(account_), credentialKey, describeOwnerDir(account_));
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfIssuerNode, *page);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, 1, j_);
|
||||
}
|
||||
|
||||
if (subject == account_)
|
||||
{
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const page =
|
||||
view().dirInsert(keylet::ownerDir(subject), credentialKey, describeOwnerDir(subject));
|
||||
JLOG(j_.trace()) << "Adding Credential to owner directory " << to_string(credentialKey.key)
|
||||
<< ": " << (page ? "success" : "failure");
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
sleCred->setFieldU64(sfSubjectNode, *page);
|
||||
view().update(view().peek(keylet::account(subject)));
|
||||
}
|
||||
|
||||
view().insert(sleCred);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// ------- DELETE --------------------------
|
||||
|
||||
std::uint32_t
|
||||
CredentialDelete::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// 0 means "Allow any flags"
|
||||
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CredentialDelete::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const subject = ctx.tx[~sfSubject];
|
||||
auto const issuer = ctx.tx[~sfIssuer];
|
||||
|
||||
if (!subject && !issuer)
|
||||
{
|
||||
// Neither field is present, the transaction is malformed.
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: "
|
||||
"No Subject or Issuer fields.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// Make sure that the passed account is valid.
|
||||
if ((subject && subject->isZero()) || (issuer && issuer->isZero()))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: Subject or Issuer "
|
||||
"field zeroed.";
|
||||
return temINVALID_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
auto const credType = ctx.tx[sfCredentialType];
|
||||
if (credType.empty() || (credType.size() > maxCredentialTypeLength))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: invalid size of CredentialType.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialDelete::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const account{ctx.tx[sfAccount]};
|
||||
auto const subject = ctx.tx[~sfSubject].value_or(account);
|
||||
auto const issuer = ctx.tx[~sfIssuer].value_or(account);
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
|
||||
if (!ctx.view.exists(keylet::credential(subject, issuer, credType)))
|
||||
return tecNO_ENTRY;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialDelete::doApply()
|
||||
{
|
||||
auto const subject = ctx_.tx[~sfSubject].value_or(account_);
|
||||
auto const issuer = ctx_.tx[~sfIssuer].value_or(account_);
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
auto const sleCred = view().peek(keylet::credential(subject, issuer, credType));
|
||||
if (!sleCred)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if ((subject != account_) && (issuer != account_) &&
|
||||
!checkExpired(sleCred, ctx_.view().header().parentCloseTime))
|
||||
{
|
||||
JLOG(j_.trace()) << "Can't delete non-expired credential.";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return deleteSLE(view(), sleCred, j_);
|
||||
}
|
||||
|
||||
// ------- APPLY --------------------------
|
||||
|
||||
std::uint32_t
|
||||
CredentialAccept::getFlagsMask(PreflightContext const& ctx)
|
||||
{
|
||||
// 0 means "Allow any flags"
|
||||
return ctx.rules.enabled(fixInvalidTxFlags) ? tfUniversalMask : 0;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
CredentialAccept::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.tx[sfIssuer])
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: Issuer field zeroed.";
|
||||
return temINVALID_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
auto const credType = ctx.tx[sfCredentialType];
|
||||
if (credType.empty() || (credType.size() > maxCredentialTypeLength))
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: invalid size of CredentialType.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
CredentialAccept::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
AccountID const subject = ctx.tx[sfAccount];
|
||||
AccountID const issuer = ctx.tx[sfIssuer];
|
||||
auto const credType(ctx.tx[sfCredentialType]);
|
||||
|
||||
if (!ctx.view.exists(keylet::account(issuer)))
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "No issuer: " << to_string(issuer);
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
auto const sleCred = ctx.view.read(keylet::credential(subject, issuer, credType));
|
||||
if (!sleCred)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "No credential: " << to_string(subject) << ", " << to_string(issuer)
|
||||
<< ", " << credType;
|
||||
return tecNO_ENTRY;
|
||||
}
|
||||
|
||||
if (sleCred->getFieldU32(sfFlags) & lsfAccepted)
|
||||
{
|
||||
JLOG(ctx.j.warn()) << "Credential already accepted: " << to_string(subject) << ", "
|
||||
<< to_string(issuer) << ", " << credType;
|
||||
return tecDUPLICATE;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
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(account_));
|
||||
auto const sleIssuer = view().peek(keylet::account(issuer));
|
||||
|
||||
if (!sleSubject || !sleIssuer)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
STAmount const reserve{
|
||||
view().fees().accountReserve(sleSubject->getFieldU32(sfOwnerCount) + 1)};
|
||||
if (mPriorBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
auto const credType(ctx_.tx[sfCredentialType]);
|
||||
Keylet const credentialKey = keylet::credential(account_, issuer, credType);
|
||||
auto const sleCred = view().peek(credentialKey); // Checked in preclaim()
|
||||
|
||||
if (checkExpired(sleCred, view().header().parentCloseTime))
|
||||
{
|
||||
JLOG(j_.trace()) << "Credential is expired: " << sleCred->getText();
|
||||
// delete expired credentials even if the transaction failed
|
||||
auto const err = credentials::deleteSLE(view(), sleCred, j_);
|
||||
return isTesSuccess(err) ? tecEXPIRED : err;
|
||||
}
|
||||
|
||||
sleCred->setFieldU32(sfFlags, lsfAccepted);
|
||||
view().update(sleCred);
|
||||
|
||||
adjustOwnerCount(view(), sleIssuer, -1, j_);
|
||||
adjustOwnerCount(view(), sleSubject, 1, j_);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
59
src/libxrpl/tx/transactors/did/DIDDelete.cpp
Normal file
59
src/libxrpl/tx/transactors/did/DIDDelete.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/tx/transactors/did/DIDDelete.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
DIDDelete::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner)
|
||||
{
|
||||
auto const sle = ctx.view().peek(sleKeylet);
|
||||
if (!sle)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
return DIDDelete::deleteSLE(ctx.view(), sle, owner, ctx.journal);
|
||||
}
|
||||
|
||||
TER
|
||||
DIDDelete::deleteSLE(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sle,
|
||||
AccountID const owner,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Remove object from owner directory
|
||||
if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Unable to delete DID Token from owner.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleOwner = view.peek(keylet::account(owner));
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
view.update(sleOwner);
|
||||
|
||||
// Remove object from ledger
|
||||
view.erase(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
DIDDelete::doApply()
|
||||
{
|
||||
return deleteSLE(ctx_, keylet::did(account_), account_);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/did/DID.h>
|
||||
#include <xrpl/tx/transactors/did/DIDSet.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -134,54 +134,4 @@ DIDSet::doApply()
|
||||
return addSLE(ctx_, sleDID, account_);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
DIDDelete::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
DIDDelete::deleteSLE(ApplyContext& ctx, Keylet sleKeylet, AccountID const owner)
|
||||
{
|
||||
auto const sle = ctx.view().peek(sleKeylet);
|
||||
if (!sle)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
return DIDDelete::deleteSLE(ctx.view(), sle, owner, ctx.journal);
|
||||
}
|
||||
|
||||
TER
|
||||
DIDDelete::deleteSLE(
|
||||
ApplyView& view,
|
||||
std::shared_ptr<SLE> sle,
|
||||
AccountID const owner,
|
||||
beast::Journal j)
|
||||
{
|
||||
// Remove object from owner directory
|
||||
if (!view.dirRemove(keylet::ownerDir(owner), (*sle)[sfOwnerNode], sle->key(), true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Unable to delete DID Token from owner.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const sleOwner = view.peek(keylet::account(owner));
|
||||
if (!sleOwner)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
adjustOwnerCount(view, sleOwner, -1, j);
|
||||
view.update(sleOwner);
|
||||
|
||||
// Remove object from ledger
|
||||
view.erase(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
DIDDelete::doApply()
|
||||
{
|
||||
return deleteSLE(ctx_, keylet::did(account_), account_);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
File diff suppressed because it is too large
Load Diff
201
src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp
Normal file
201
src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/tx/transactors/escrow/EscrowCancel.h>
|
||||
|
||||
#include <libxrpl/tx/transactors/escrow/EscrowHelpers.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
EscrowCancel::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowCancelPreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCancelPreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), account); ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCancelPreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecINTERNAL
|
||||
if (issuer == account)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCancel::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCancelPreclaimHelper<T>(ctx, account, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCancel::doApply()
|
||||
{
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecNO_TARGET;
|
||||
}
|
||||
|
||||
auto const now = ctx_.view().header().parentCloseTime;
|
||||
|
||||
// No cancel time specified: can't execute at all.
|
||||
if (!(*slep)[~sfCancelAfter])
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Too soon: can't execute before the cancel time.
|
||||
if (!after(now, (*slep)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Remove escrow from recipient's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfDestinationNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir((*slep)[sfDestination]), *optPage, k.key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
|
||||
// Transfer amount back to the owner
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] + amount;
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = account == account_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
parityRate,
|
||||
slep,
|
||||
mPriorBalance,
|
||||
amount,
|
||||
issuer,
|
||||
account, // sender and receiver are the same
|
||||
account,
|
||||
createAsset,
|
||||
j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret; // LCOV_EXCL_LINE
|
||||
|
||||
// Remove escrow from issuers owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
498
src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp
Normal file
498
src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp
Normal file
@@ -0,0 +1,498 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/conditions/Condition.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/transactors/escrow/EscrowCreate.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
Escrow
|
||||
======
|
||||
|
||||
Escrow is a feature of the XRP Ledger that allows you to send conditional
|
||||
XRP payments. These conditional payments, called escrows, set aside XRP and
|
||||
deliver it later when certain conditions are met. Conditions to successfully
|
||||
finish an escrow include time-based unlocks and crypto-conditions. Escrows
|
||||
can also be set to expire if not finished in time.
|
||||
|
||||
The XRP set aside in an escrow is locked up. No one can use or destroy the
|
||||
XRP until the escrow has been successfully finished or canceled. Before the
|
||||
expiration time, only the intended receiver can get the XRP. After the
|
||||
expiration time, the XRP can only be returned to the sender.
|
||||
|
||||
For more details on escrow, including examples, diagrams and more please
|
||||
visit https://xrpl.org/escrow.html
|
||||
|
||||
For details on specific transactions, including fields and validation rules
|
||||
please see:
|
||||
|
||||
`EscrowCreate`
|
||||
--------------
|
||||
See: https://xrpl.org/escrowcreate.html
|
||||
|
||||
`EscrowFinish`
|
||||
--------------
|
||||
See: https://xrpl.org/escrowfinish.html
|
||||
|
||||
`EscrowCancel`
|
||||
--------------
|
||||
See: https://xrpl.org/escrowcancel.html
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TxConsequences
|
||||
EscrowCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
return TxConsequences{ctx.tx, isXRP(amount) ? amount.xrp() : beast::zero};
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static NotTEC
|
||||
escrowCreatePreflightHelper(PreflightContext const& ctx);
|
||||
|
||||
template <>
|
||||
NotTEC
|
||||
escrowCreatePreflightHelper<Issue>(PreflightContext const& ctx)
|
||||
{
|
||||
STAmount const amount = ctx.tx[sfAmount];
|
||||
if (amount.native() || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (badCurrency() == amount.getCurrency())
|
||||
return temBAD_CURRENCY;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
NotTEC
|
||||
escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||
return temDISABLED;
|
||||
|
||||
auto const amount = ctx.tx[sfAmount];
|
||||
if (amount.native() || amount.mpt() > MPTAmount{maxMPTokenAmount} || amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.rules.enabled(featureTokenEscrow))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) { return escrowCreatePreflightHelper<T>(ctx); },
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (amount <= beast::zero)
|
||||
return temBAD_AMOUNT;
|
||||
}
|
||||
|
||||
// We must specify at least one timeout value
|
||||
if (!ctx.tx[~sfCancelAfter] && !ctx.tx[~sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
// If both finish and cancel times are specified then the cancel time must
|
||||
// be strictly after the finish time.
|
||||
if (ctx.tx[~sfCancelAfter] && ctx.tx[~sfFinishAfter] &&
|
||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
// In the absence of a FinishAfter, the escrow can be finished
|
||||
// immediately, which can be confusing. When creating an escrow,
|
||||
// we want to ensure that either a FinishAfter time is explicitly
|
||||
// specified or a completion condition is attached.
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
|
||||
return temMALFORMED;
|
||||
|
||||
if (auto const cb = ctx.tx[~sfCondition])
|
||||
{
|
||||
using namespace xrpl::cryptoconditions;
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
auto condition = Condition::deserialize(*cb, ec);
|
||||
if (!condition)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Malformed condition during escrow creation: " << ec.message();
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowCreatePreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCreatePreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the lsfAllowTrustLineLocking is not enabled, return tecNO_PERMISSION
|
||||
auto const sleIssuer = ctx.view.read(keylet::account(issuer));
|
||||
if (!sleIssuer)
|
||||
return tecNO_ISSUER;
|
||||
if (!sleIssuer->isFlag(lsfAllowTrustLineLocking))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the account does not have a trustline to the issuer, return tecNO_LINE
|
||||
auto const sleRippleState = ctx.view.read(keylet::line(account, issuer, amount.getCurrency()));
|
||||
if (!sleRippleState)
|
||||
return tecNO_LINE;
|
||||
|
||||
STAmount const balance = (*sleRippleState)[sfBalance];
|
||||
|
||||
// If balance is positive, issuer must have higher address than account
|
||||
if (balance > beast::zero && issuer < account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If balance is negative, issuer must have lower address than account
|
||||
if (balance < beast::zero && issuer > account)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), account); ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecFROZEN
|
||||
if (isFrozen(ctx.view, account, amount.issue()))
|
||||
return tecFROZEN;
|
||||
|
||||
// If the issuer has frozen the destination, return tecFROZEN
|
||||
if (isFrozen(ctx.view, dest, amount.issue()))
|
||||
return tecFROZEN;
|
||||
|
||||
STAmount const spendableAmount =
|
||||
accountHolds(ctx.view, account, amount.getCurrency(), issuer, fhIGNORE_FREEZE, ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the amount is not addable to the balance, return tecPRECISION_LOSS
|
||||
if (!canAdd(spendableAmount, amount))
|
||||
return tecPRECISION_LOSS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowCreatePreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& account,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tecNO_PERMISSION
|
||||
if (issuer == account)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the lsfMPTCanEscrow is not enabled, return tecNO_PERMISSION
|
||||
if (!sleIssuance->isFlag(lsfMPTCanEscrow))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// If the issuer is not the same as the issuer of the mpt, return
|
||||
// tecNO_PERMISSION
|
||||
if (sleIssuance->getAccountID(sfIssuer) != issuer)
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
|
||||
// If the account does not have the mpt, return tecOBJECT_NOT_FOUND
|
||||
if (!ctx.view.exists(keylet::mptoken(issuanceKey.key, account)))
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the account is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||
ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the account, return tecLOCKED
|
||||
if (isFrozen(ctx.view, account, mptIssue))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, mptIssue))
|
||||
return tecLOCKED;
|
||||
|
||||
// If the mpt cannot be transferred, return tecNO_AUTH
|
||||
if (auto const ter = canTransfer(ctx.view, mptIssue, account, dest); ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
STAmount const spendableAmount = accountHolds(
|
||||
ctx.view, account, amount.get<MPTIssue>(), fhIGNORE_FREEZE, ahIGNORE_AUTH, ctx.j);
|
||||
|
||||
// If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount <= beast::zero)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
// If the spendable amount is less than the amount, return
|
||||
// tecINSUFFICIENT_FUNDS
|
||||
if (spendableAmount < amount)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
STAmount const amount{ctx.tx[sfAmount]};
|
||||
AccountID const account{ctx.tx[sfAccount]};
|
||||
AccountID const dest{ctx.tx[sfDestination]};
|
||||
|
||||
auto const sled = ctx.view.read(keylet::account(dest));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
// Pseudo-accounts cannot receive escrow. 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(sled))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureTokenEscrow))
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowCreatePreclaimHelper<T>(ctx, account, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowLockApplyHelper(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowLockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = rippleCredit(
|
||||
view, sender, issuer, amount, amount.holds<MPTIssue>() ? false : true, journal);
|
||||
if (ter != tesSUCCESS)
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowLockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
STAmount const& amount,
|
||||
beast::Journal journal)
|
||||
{
|
||||
// Defensive: Issuer cannot create an escrow
|
||||
if (issuer == sender)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const ter = rippleLockEscrowMPT(view, sender, amount, journal);
|
||||
if (ter != tesSUCCESS)
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::doApply()
|
||||
{
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime;
|
||||
|
||||
if (ctx_.tx[~sfCancelAfter] && after(closeTime, ctx_.tx[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx_.tx[~sfFinishAfter] && after(closeTime, ctx_.tx[sfFinishAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(account_));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (mSourceBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Check reserve and funds availability
|
||||
if (isXRP(amount))
|
||||
{
|
||||
if (mSourceBalance < reserve + STAmount(amount).xrp())
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
// Check destination account
|
||||
{
|
||||
auto const sled = ctx_.view().read(keylet::account(ctx_.tx[sfDestination]));
|
||||
if (!sled)
|
||||
return tecNO_DST; // LCOV_EXCL_LINE
|
||||
if (((*sled)[sfFlags] & lsfRequireDestTag) && !ctx_.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
}
|
||||
|
||||
// Create escrow in ledger. Note that we we use the value from the
|
||||
// sequence or ticket. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const escrowKeylet = keylet::escrow(account_, ctx_.tx.getSeqValue());
|
||||
auto const slep = std::make_shared<SLE>(escrowKeylet);
|
||||
(*slep)[sfAmount] = amount;
|
||||
(*slep)[sfAccount] = account_;
|
||||
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
|
||||
}
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
|
||||
{
|
||||
auto const xferRate = transferRate(ctx_.view(), amount);
|
||||
if (xferRate != parityRate)
|
||||
(*slep)[sfTransferRate] = xferRate.value;
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
|
||||
// Add escrow to sender's owner directory
|
||||
{
|
||||
auto page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account_), escrowKeylet, describeOwnerDir(account_));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
// If it's not a self-send, add escrow to recipient's owner directory.
|
||||
AccountID const dest = ctx_.tx[sfDestination];
|
||||
if (dest != account_)
|
||||
{
|
||||
auto page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dest), escrowKeylet, describeOwnerDir(dest));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
// IOU escrow objects are added to the issuer's owner directory to help
|
||||
// track the total locked balance. For MPT, this isn't necessary because the
|
||||
// locked balance is already stored directly in the MPTokenIssuance object.
|
||||
AccountID const issuer = amount.getIssuer();
|
||||
if (!isXRP(amount) && issuer != account_ && issuer != dest && !amount.holds<MPTIssue>())
|
||||
{
|
||||
auto page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(issuer), escrowKeylet, describeOwnerDir(issuer));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfIssuerNode] = *page;
|
||||
}
|
||||
|
||||
// Deduct owner's balance
|
||||
if (isXRP(amount))
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - amount;
|
||||
else
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowLockApplyHelper<T>(ctx_.view(), issuer, account_, amount, j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
{
|
||||
return ret; // LCOV_EXCL_LINE
|
||||
}
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
372
src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp
Normal file
372
src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/conditions/Condition.h>
|
||||
#include <xrpl/conditions/Fulfillment.h>
|
||||
#include <xrpl/core/HashRouter.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/transactors/escrow/EscrowFinish.h>
|
||||
|
||||
#include <libxrpl/tx/transactors/escrow/EscrowHelpers.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
// During an EscrowFinish, the transaction must specify both
|
||||
// a condition and a fulfillment. We track whether that
|
||||
// fulfillment matches and validates the condition.
|
||||
constexpr HashRouterFlags SF_CF_INVALID = HashRouterFlags::PRIVATE5;
|
||||
constexpr HashRouterFlags SF_CF_VALID = HashRouterFlags::PRIVATE6;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static bool
|
||||
checkCondition(Slice f, Slice c)
|
||||
{
|
||||
using namespace xrpl::cryptoconditions;
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
auto condition = Condition::deserialize(c, ec);
|
||||
if (!condition)
|
||||
return false;
|
||||
|
||||
auto fulfillment = Fulfillment::deserialize(f, ec);
|
||||
if (!fulfillment)
|
||||
return false;
|
||||
|
||||
return validate(*fulfillment, *condition);
|
||||
}
|
||||
|
||||
bool
|
||||
EscrowFinish::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const cb = ctx.tx[~sfCondition];
|
||||
auto const fb = ctx.tx[~sfFulfillment];
|
||||
|
||||
// If you specify a condition, then you must also specify
|
||||
// a fulfillment.
|
||||
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowFinish::preflightSigValidated(PreflightContext const& ctx)
|
||||
{
|
||||
auto const cb = ctx.tx[~sfCondition];
|
||||
auto const fb = ctx.tx[~sfFulfillment];
|
||||
|
||||
if (cb && fb)
|
||||
{
|
||||
auto& router = ctx.registry.getHashRouter();
|
||||
|
||||
auto const id = ctx.tx.getTransactionID();
|
||||
auto const flags = router.getFlags(id);
|
||||
|
||||
// If we haven't checked the condition, check it
|
||||
// now. Whether it passes or not isn't important
|
||||
// in preflight.
|
||||
if (!any(flags & (SF_CF_INVALID | SF_CF_VALID)))
|
||||
{
|
||||
if (checkCondition(*fb, *cb))
|
||||
router.setFlags(id, SF_CF_VALID);
|
||||
else
|
||||
router.setFlags(id, SF_CF_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount extraFee{0};
|
||||
|
||||
if (auto const fb = tx[~sfFulfillment])
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
template <ValidIssueType T>
|
||||
static TER
|
||||
escrowFinishPreclaimHelper(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount);
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowFinishPreclaimHelper<Issue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the account, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is authorized
|
||||
if (auto const ter = requireAuth(ctx.view, amount.issue(), dest); ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
// If the issuer has deep frozen the destination, return tecFROZEN
|
||||
if (isDeepFrozen(ctx.view, dest, amount.getCurrency(), amount.getIssuer()))
|
||||
return tecFROZEN;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
TER
|
||||
escrowFinishPreclaimHelper<MPTIssue>(
|
||||
PreclaimContext const& ctx,
|
||||
AccountID const& dest,
|
||||
STAmount const& amount)
|
||||
{
|
||||
AccountID issuer = amount.getIssuer();
|
||||
// If the issuer is the same as the dest, return tesSUCCESS
|
||||
if (issuer == dest)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If the mpt does not exist, return tecOBJECT_NOT_FOUND
|
||||
auto const issuanceKey = keylet::mptIssuance(amount.get<MPTIssue>().getMptID());
|
||||
auto const sleIssuance = ctx.view.read(issuanceKey);
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// If the issuer has requireAuth set, check if the destination is
|
||||
// authorized
|
||||
auto const& mptIssue = amount.get<MPTIssue>();
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||
ter != tesSUCCESS)
|
||||
return ter;
|
||||
|
||||
// If the issuer has frozen the destination, return tecLOCKED
|
||||
if (isFrozen(ctx.view, dest, mptIssue))
|
||||
return tecLOCKED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (ctx.view.rules().enabled(featureCredentials))
|
||||
{
|
||||
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowFinish::doApply()
|
||||
{
|
||||
auto const k = keylet::escrow(ctx_.tx[sfOwner], ctx_.tx[sfOfferSequence]);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecNO_TARGET;
|
||||
}
|
||||
|
||||
// If a cancel time is present, a finish operation should only succeed prior
|
||||
// to that time.
|
||||
auto const now = ctx_.view().header().parentCloseTime;
|
||||
|
||||
// Too soon: can't execute before the finish time
|
||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Too late: can't execute after the cancel time
|
||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
{
|
||||
auto const id = ctx_.tx.getTransactionID();
|
||||
auto flags = ctx_.registry.getHashRouter().getFlags(id);
|
||||
|
||||
auto const cb = ctx_.tx[~sfCondition];
|
||||
|
||||
// It's unlikely that the results of the check will
|
||||
// expire from the hash router, but if it happens,
|
||||
// simply re-run the check.
|
||||
if (cb && !any(flags & (SF_CF_INVALID | SF_CF_VALID)))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
auto const fb = ctx_.tx[~sfFulfillment];
|
||||
|
||||
if (!fb)
|
||||
return tecINTERNAL;
|
||||
|
||||
if (checkCondition(*fb, *cb))
|
||||
flags = SF_CF_VALID;
|
||||
else
|
||||
flags = SF_CF_INVALID;
|
||||
|
||||
ctx_.registry.getHashRouter().setFlags(id, flags);
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
// If the check failed, then simply return an error
|
||||
// and don't look at anything else.
|
||||
if (any(flags & SF_CF_INVALID))
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
|
||||
// Check against condition in the ledger entry:
|
||||
auto const cond = (*slep)[~sfCondition];
|
||||
|
||||
// If a condition wasn't specified during creation,
|
||||
// one shouldn't be included now.
|
||||
if (!cond && cb)
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
|
||||
// If a condition was specified during creation of
|
||||
// the suspended payment, the identical condition
|
||||
// must be presented again. We don't check if the
|
||||
// fulfillment matches the condition since we did
|
||||
// that in preflight.
|
||||
if (cond && (cond != cb))
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err = verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
|
||||
// Remove escrow from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(account), page, k.key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from owner.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Remove escrow from recipient's owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfDestinationNode])
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(destID), *optPage, k.key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
STAmount const amount = slep->getFieldAmount(sfAmount);
|
||||
// Transfer amount to destination
|
||||
if (isXRP(amount))
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + amount;
|
||||
else
|
||||
{
|
||||
if (!ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
return temDISABLED; // LCOV_EXCL_LINE
|
||||
|
||||
Rate lockedRate = slep->isFieldPresent(sfTransferRate)
|
||||
? xrpl::Rate(slep->getFieldU32(sfTransferRate))
|
||||
: parityRate;
|
||||
auto const issuer = amount.getIssuer();
|
||||
bool const createAsset = destID == account_;
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowUnlockApplyHelper<T>(
|
||||
ctx_.view(),
|
||||
lockedRate,
|
||||
sled,
|
||||
mPriorBalance,
|
||||
amount,
|
||||
issuer,
|
||||
account,
|
||||
destID,
|
||||
createAsset,
|
||||
j_);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
// Remove escrow from issuers owner directory, if present.
|
||||
if (auto const optPage = (*slep)[~sfIssuerNode]; optPage)
|
||||
{
|
||||
if (!ctx_.view().dirRemove(keylet::ownerDir(issuer), *optPage, k.key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j_.fatal()) << "Unable to delete Escrow from recipient.";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
ctx_.view().erase(slep);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
229
src/libxrpl/tx/transactors/escrow/EscrowHelpers.h
Normal file
229
src/libxrpl/tx/transactors/escrow/EscrowHelpers.h
Normal file
@@ -0,0 +1,229 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/MPTAmount.h>
|
||||
#include <xrpl/tx/transactors/token/MPTokenAuthorize.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
template <ValidIssueType T>
|
||||
TER
|
||||
escrowUnlockApplyHelper(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal);
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<Issue>(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
Keylet const trustLineKey = keylet::line(receiver, amount.issue());
|
||||
bool const recvLow = issuer > receiver;
|
||||
bool const senderIssuer = issuer == sender;
|
||||
bool const receiverIssuer = issuer == receiver;
|
||||
bool const issuerHigh = issuer > receiver;
|
||||
|
||||
if (senderIssuer)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (receiverIssuer)
|
||||
return tesSUCCESS;
|
||||
|
||||
if (!view.exists(trustLineKey) && createAsset && !receiverIssuer)
|
||||
{
|
||||
// Can the account cover the trust line's reserve?
|
||||
if (std::uint32_t const ownerCount = {sleDest->at(sfOwnerCount)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
JLOG(journal.trace()) << "Trust line does not exist. "
|
||||
"Insufficient reserve to create line.";
|
||||
|
||||
return tecNO_LINE_INSUF_RESERVE;
|
||||
}
|
||||
|
||||
Currency const currency = amount.getCurrency();
|
||||
STAmount initialBalance(amount.issue());
|
||||
initialBalance.setIssuer(noAccount());
|
||||
|
||||
// clang-format off
|
||||
if (TER const ter = trustCreate(
|
||||
view, // payment sandbox
|
||||
recvLow, // is dest low?
|
||||
issuer, // source
|
||||
receiver, // destination
|
||||
trustLineKey.key, // ledger index
|
||||
sleDest, // Account to add to
|
||||
false, // authorize account
|
||||
(sleDest->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, receiver), // limit of zero
|
||||
0, // quality in
|
||||
0, // quality out
|
||||
journal); // journal
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
// clang-format on
|
||||
|
||||
view.update(sleDest);
|
||||
}
|
||||
|
||||
if (!view.exists(trustLineKey) && !receiverIssuer)
|
||||
return tecNO_LINE;
|
||||
|
||||
auto const xferRate = transferRate(view, amount);
|
||||
// update if issuer rate is less than locked rate
|
||||
if (xferRate < lockedRate)
|
||||
lockedRate = xferRate;
|
||||
|
||||
// Transfer Rate only applies when:
|
||||
// 1. Issuer is not involved in the transfer (senderIssuer or
|
||||
// receiverIssuer)
|
||||
// 2. The locked rate is different from the parity rate
|
||||
|
||||
// NOTE: Transfer fee in escrow works a bit differently from a normal
|
||||
// payment. In escrow, the fee is deducted from the locked/sending amount,
|
||||
// whereas in a normal payment, the transfer fee is taken on top of the
|
||||
// sending amount.
|
||||
auto finalAmt = amount;
|
||||
if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate)
|
||||
{
|
||||
// compute transfer fee, if any
|
||||
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.issue(), true);
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
|
||||
// validate the line limit if the account submitting txn is not the receiver
|
||||
// of the funds
|
||||
if (!createAsset)
|
||||
{
|
||||
auto const sleRippleState = view.peek(trustLineKey);
|
||||
if (!sleRippleState)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// if the issuer is the high, then we use the low limit
|
||||
// otherwise we use the high limit
|
||||
STAmount const lineLimit =
|
||||
sleRippleState->getFieldAmount(issuerHigh ? sfLowLimit : sfHighLimit);
|
||||
|
||||
STAmount lineBalance = sleRippleState->getFieldAmount(sfBalance);
|
||||
|
||||
// flip the sign of the line balance if the issuer is not high
|
||||
if (!issuerHigh)
|
||||
lineBalance.negate();
|
||||
|
||||
// add the final amount to the line balance
|
||||
lineBalance += finalAmt;
|
||||
|
||||
// if the transfer would exceed the line limit return tecLIMIT_EXCEEDED
|
||||
if (lineLimit < lineBalance)
|
||||
return tecLIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
// if destination is not the issuer then transfer funds
|
||||
if (!receiverIssuer)
|
||||
{
|
||||
auto const ter = rippleCredit(view, issuer, receiver, finalAmt, true, journal);
|
||||
if (ter != tesSUCCESS)
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline TER
|
||||
escrowUnlockApplyHelper<MPTIssue>(
|
||||
ApplyView& view,
|
||||
Rate lockedRate,
|
||||
std::shared_ptr<SLE> const& sleDest,
|
||||
STAmount const& xrpBalance,
|
||||
STAmount const& amount,
|
||||
AccountID const& issuer,
|
||||
AccountID const& sender,
|
||||
AccountID const& receiver,
|
||||
bool createAsset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
bool const senderIssuer = issuer == sender;
|
||||
bool const receiverIssuer = issuer == receiver;
|
||||
|
||||
auto const mptID = amount.get<MPTIssue>().getMptID();
|
||||
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)};
|
||||
xrpBalance < view.fees().accountReserve(ownerCount + 1))
|
||||
{
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
}
|
||||
|
||||
if (auto const ter = MPTokenAuthorize::createMPToken(view, mptID, receiver, 0);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
return ter; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// update owner count.
|
||||
adjustOwnerCount(view, sleDest, 1, journal);
|
||||
}
|
||||
|
||||
if (!view.exists(keylet::mptoken(issuanceKey.key, receiver)) && !receiverIssuer)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const xferRate = transferRate(view, amount);
|
||||
// update if issuer rate is less than locked rate
|
||||
if (xferRate < lockedRate)
|
||||
lockedRate = xferRate;
|
||||
|
||||
// Transfer Rate only applies when:
|
||||
// 1. Issuer is not involved in the transfer (senderIssuer or
|
||||
// receiverIssuer)
|
||||
// 2. The locked rate is different from the parity rate
|
||||
|
||||
// NOTE: Transfer fee in escrow works a bit differently from a normal
|
||||
// payment. In escrow, the fee is deducted from the locked/sending amount,
|
||||
// whereas in a normal payment, the transfer fee is taken on top of the
|
||||
// sending amount.
|
||||
auto finalAmt = amount;
|
||||
if ((!senderIssuer && !receiverIssuer) && lockedRate != parityRate)
|
||||
{
|
||||
// compute transfer fee, if any
|
||||
auto const xferFee = amount.value() - divideRound(amount, lockedRate, amount.asset(), true);
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
return rippleUnlockEscrowMPT(
|
||||
view,
|
||||
sender,
|
||||
receiver,
|
||||
finalAmt,
|
||||
view.rules().enabled(fixTokenEscrowV1) ? amount : finalAmt,
|
||||
journal);
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,542 +0,0 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PayChan.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
PaymentChannel
|
||||
|
||||
Payment channels permit off-ledger checkpoints of XRP payments flowing
|
||||
in a single direction. A channel sequesters the owner's XRP in its own
|
||||
ledger entry. The owner can authorize the recipient to claim up to a
|
||||
given balance by giving the receiver a signed message (off-ledger). The
|
||||
recipient can use this signed message to claim any unpaid balance while
|
||||
the channel remains open. The owner can top off the line as needed. If
|
||||
the channel has not paid out all its funds, the owner must wait out a
|
||||
delay to close the channel to give the recipient a chance to supply any
|
||||
claims. The recipient can close the channel at any time. Any transaction
|
||||
that touches the channel after the expiration time will close the
|
||||
channel. The total amount paid increases monotonically as newer claims
|
||||
are issued. When the channel is closed any remaining balance is returned
|
||||
to the owner. Channels are intended to permit intermittent off-ledger
|
||||
settlement of ILP trust lines as balances get substantial. For
|
||||
bidirectional channels, a payment channel can be used in each direction.
|
||||
|
||||
PaymentChannelCreate
|
||||
|
||||
Create a unidirectional channel. The parameters are:
|
||||
Destination
|
||||
The recipient at the end of the channel.
|
||||
Amount
|
||||
The amount of XRP to deposit in the channel immediately.
|
||||
SettleDelay
|
||||
The amount of time everyone but the recipient must wait for a
|
||||
superior claim.
|
||||
PublicKey
|
||||
The key that will sign claims against the channel.
|
||||
CancelAfter (optional)
|
||||
Any channel transaction that touches this channel after the
|
||||
`CancelAfter` time will close it.
|
||||
DestinationTag (optional)
|
||||
Destination tags allow the different accounts inside of a Hosted
|
||||
Wallet to be mapped back onto the Ripple ledger. The destination tag
|
||||
tells the server to which account in the Hosted Wallet the funds are
|
||||
intended to go to. Required if the destination has lsfRequireDestTag
|
||||
set.
|
||||
SourceTag (optional)
|
||||
Source tags allow the different accounts inside of a Hosted Wallet
|
||||
to be mapped back onto the Ripple ledger. Source tags are similar to
|
||||
destination tags but are for the channel owner to identify their own
|
||||
transactions.
|
||||
|
||||
PaymentChannelFund
|
||||
|
||||
Add additional funds to the payment channel. Only the channel owner may
|
||||
use this transaction. The parameters are:
|
||||
Channel
|
||||
The 256-bit ID of the channel.
|
||||
Amount
|
||||
The amount of XRP to add.
|
||||
Expiration (optional)
|
||||
Time the channel closes. The transaction will fail if the expiration
|
||||
times does not satisfy the SettleDelay constraints.
|
||||
|
||||
PaymentChannelClaim
|
||||
|
||||
Place a claim against an existing channel. The parameters are:
|
||||
Channel
|
||||
The 256-bit ID of the channel.
|
||||
Balance (optional)
|
||||
The total amount of XRP delivered after this claim is processed
|
||||
(optional, not needed if just closing). Amount (optional) The amount of XRP
|
||||
the signature is for (not needed if equal to Balance or just closing the
|
||||
line). Signature (optional) Authorization for the balance above, signed by
|
||||
the owner (optional, not needed if closing or owner is performing the
|
||||
transaction). The signature if for the following message: CLM\0 followed by
|
||||
the 256-bit channel ID, and a 64-bit integer drops. PublicKey (optional) The
|
||||
public key that made the signature (optional, required if a signature is
|
||||
present) Flags tfClose Request that the channel be closed tfRenew Request
|
||||
that the channel's expiration be reset. Only the owner may renew a channel.
|
||||
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static TER
|
||||
closeChannel(
|
||||
std::shared_ptr<SLE> const& slep,
|
||||
ApplyView& view,
|
||||
uint256 const& key,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
// Remove PayChan from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!view.dirRemove(keylet::ownerDir(src), page, key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not remove paychan from src owner directory";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Remove PayChan from recipient's owner directory, if present.
|
||||
if (auto const page = (*slep)[~sfDestinationNode])
|
||||
{
|
||||
auto const dst = (*slep)[sfDestination];
|
||||
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not remove paychan from dst owner directory";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = view.peek(keylet::account(src));
|
||||
if (!sle)
|
||||
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);
|
||||
|
||||
// Remove PayChan from ledger
|
||||
view.erase(slep);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TxConsequences
|
||||
PayChanCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PayChanCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
return temDST_IS_SRC;
|
||||
|
||||
if (!publicKeyType(ctx.tx[sfPublicKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const sle = ctx.view.read(keylet::account(account));
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check reserve and funds availability
|
||||
{
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
auto const dst = ctx.tx[sfDestination];
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read(keylet::account(dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
auto const flags = sled->getFlags();
|
||||
|
||||
// Check if they have disallowed incoming payment channels
|
||||
if (flags & lsfDisallowIncomingPayChan)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if ((flags & lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
// Pseudo-accounts cannot receive payment channels, other than native
|
||||
// to their underlying ledger object - implemented in their respective
|
||||
// transaction types. 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(sled))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanCreate::doApply()
|
||||
{
|
||||
auto const account = ctx_.tx[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (ctx_.view().rules().enabled(fixPayChanCancelAfter))
|
||||
{
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime;
|
||||
if (ctx_.tx[~sfCancelAfter] && after(closeTime, ctx_.tx[sfCancelAfter]))
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
|
||||
// Create PayChan in ledger.
|
||||
//
|
||||
// Note that we we use the value from the sequence or ticket as the
|
||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const payChanKeylet = keylet::payChan(account, dst, ctx_.tx.getSeqValue());
|
||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||
|
||||
// Funds held in this channel
|
||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||
// Amount channel has already paid
|
||||
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed();
|
||||
(*slep)[sfAccount] = account;
|
||||
(*slep)[sfDestination] = dst;
|
||||
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
|
||||
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
|
||||
// Add PayChan to owner directory
|
||||
{
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account), payChanKeylet, describeOwnerDir(account));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
// Add PayChan to the recipient's owner directory
|
||||
{
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TxConsequences
|
||||
PayChanFund::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PayChanFund::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanFund::doApply()
|
||||
{
|
||||
Keylet const k(ltPAYCHAN, ctx_.tx[sfChannel]);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
auto const expiration = (*slep)[~sfExpiration];
|
||||
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) || (expiration && closeTime >= *expiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
}
|
||||
|
||||
if (src != txAccount)
|
||||
// only the owner can add funds or extend
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (auto extend = ctx_.tx[~sfExpiration])
|
||||
{
|
||||
auto minExpiration = ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
(*slep)[sfSettleDelay];
|
||||
if (expiration && *expiration < minExpiration)
|
||||
minExpiration = *expiration;
|
||||
|
||||
if (*extend < minExpiration)
|
||||
return temBAD_EXPIRATION;
|
||||
(*slep)[~sfExpiration] = *extend;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(txAccount));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx_.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
// do not allow adding funds if dst does not exist
|
||||
if (AccountID const dst = (*slep)[sfDestination]; !ctx_.view().read(keylet::account(dst)))
|
||||
{
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view().update(slep);
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
bool
|
||||
PayChanClaim::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
PayChanClaim::getFlagsMask(PreflightContext const&)
|
||||
{
|
||||
return tfPaymentChannelClaimMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP(*bal) || *bal <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
auto const amt = ctx.tx[~sfAmount];
|
||||
if (amt && (!isXRP(*amt) || *amt <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (bal && amt && *bal > *amt)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
{
|
||||
auto const flags = ctx.tx.getFlags();
|
||||
|
||||
if ((flags & tfClose) && (flags & tfRenew))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (auto const sig = ctx.tx[~sfSignature])
|
||||
{
|
||||
if (!(ctx.tx[~sfPublicKey] && bal))
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the signature
|
||||
// The signature isn't needed if txAccount == src, but if it's
|
||||
// present, check it
|
||||
|
||||
auto const reqBalance = bal->xrp();
|
||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
||||
|
||||
if (reqBalance > authAmt)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
Keylet const k(ltPAYCHAN, ctx.tx[sfChannel]);
|
||||
if (!publicKeyType(ctx.tx[sfPublicKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
PublicKey const pk(ctx.tx[sfPublicKey]);
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, k.key, authAmt);
|
||||
if (!verify(pk, msg.slice(), *sig))
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanClaim::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureCredentials))
|
||||
return Transactor::preclaim(ctx);
|
||||
|
||||
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanClaim::doApply()
|
||||
{
|
||||
Keylet const k(ltPAYCHAN, ctx_.tx[sfChannel]);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
AccountID const dst = (*slep)[sfDestination];
|
||||
AccountID const txAccount = ctx_.tx[sfAccount];
|
||||
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) ||
|
||||
(curExpiration && closeTime >= *curExpiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
}
|
||||
|
||||
if (txAccount != src && txAccount != dst)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx_.tx[~sfBalance])
|
||||
{
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
return temBAD_SIGNATURE;
|
||||
|
||||
if (ctx_.tx[~sfSignature])
|
||||
{
|
||||
PublicKey const pk((*slep)[sfPublicKey]);
|
||||
if (ctx_.tx[sfPublicKey] != pk)
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (reqBalance > chanFunds)
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
if (reqBalance <= chanBalance)
|
||||
// nothing requested
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
auto const sled = ctx_.view().peek(keylet::account(dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
XRPL_ASSERT(reqDelta >= beast::zero, "xrpl::PayChanClaim::doApply : minimum balance delta");
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
ctx_.view().update(sled);
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.getFlags() & tfRenew)
|
||||
{
|
||||
if (src != txAccount)
|
||||
return tecNO_PERMISSION;
|
||||
(*slep)[~sfExpiration] = std::nullopt;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.getFlags() & tfClose)
|
||||
{
|
||||
// Channel will close immediately if dry or the receiver closes
|
||||
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
|
||||
auto const settleExpiration =
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
(*slep)[sfSettleDelay];
|
||||
|
||||
if (!curExpiration || *curExpiration > settleExpiration)
|
||||
{
|
||||
(*slep)[~sfExpiration] = settleExpiration;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
186
src/libxrpl/tx/transactors/payment_channel/PayChanClaim.cpp
Normal file
186
src/libxrpl/tx/transactors/payment_channel/PayChanClaim.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PayChanClaim.h>
|
||||
|
||||
#include <libxrpl/tx/transactors/payment_channel/PayChanHelpers.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
bool
|
||||
PayChanClaim::checkExtraFeatures(PreflightContext const& ctx)
|
||||
{
|
||||
return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials);
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
PayChanClaim::getFlagsMask(PreflightContext const&)
|
||||
{
|
||||
return tfPaymentChannelClaimMask;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PayChanClaim::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
auto const bal = ctx.tx[~sfBalance];
|
||||
if (bal && (!isXRP(*bal) || *bal <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
auto const amt = ctx.tx[~sfAmount];
|
||||
if (amt && (!isXRP(*amt) || *amt <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (bal && amt && *bal > *amt)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
{
|
||||
auto const flags = ctx.tx.getFlags();
|
||||
|
||||
if ((flags & tfClose) && (flags & tfRenew))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
if (auto const sig = ctx.tx[~sfSignature])
|
||||
{
|
||||
if (!(ctx.tx[~sfPublicKey] && bal))
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the signature
|
||||
// The signature isn't needed if txAccount == src, but if it's
|
||||
// present, check it
|
||||
|
||||
auto const reqBalance = bal->xrp();
|
||||
auto const authAmt = amt ? amt->xrp() : reqBalance;
|
||||
|
||||
if (reqBalance > authAmt)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
Keylet const k(ltPAYCHAN, ctx.tx[sfChannel]);
|
||||
if (!publicKeyType(ctx.tx[sfPublicKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
PublicKey const pk(ctx.tx[sfPublicKey]);
|
||||
Serializer msg;
|
||||
serializePayChanAuthorization(msg, k.key, authAmt);
|
||||
if (!verify(pk, msg.slice(), *sig))
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanClaim::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
if (!ctx.view.rules().enabled(featureCredentials))
|
||||
return Transactor::preclaim(ctx);
|
||||
|
||||
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanClaim::doApply()
|
||||
{
|
||||
Keylet const k(ltPAYCHAN, ctx_.tx[sfChannel]);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
AccountID const dst = (*slep)[sfDestination];
|
||||
AccountID const txAccount = ctx_.tx[sfAccount];
|
||||
|
||||
auto const curExpiration = (*slep)[~sfExpiration];
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) ||
|
||||
(curExpiration && closeTime >= *curExpiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
}
|
||||
|
||||
if (txAccount != src && txAccount != dst)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx_.tx[~sfBalance])
|
||||
{
|
||||
auto const chanBalance = slep->getFieldAmount(sfBalance).xrp();
|
||||
auto const chanFunds = slep->getFieldAmount(sfAmount).xrp();
|
||||
auto const reqBalance = ctx_.tx[sfBalance].xrp();
|
||||
|
||||
if (txAccount == dst && !ctx_.tx[~sfSignature])
|
||||
return temBAD_SIGNATURE;
|
||||
|
||||
if (ctx_.tx[~sfSignature])
|
||||
{
|
||||
PublicKey const pk((*slep)[sfPublicKey]);
|
||||
if (ctx_.tx[sfPublicKey] != pk)
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (reqBalance > chanFunds)
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
if (reqBalance <= chanBalance)
|
||||
// nothing requested
|
||||
return tecUNFUNDED_PAYMENT;
|
||||
|
||||
auto const sled = ctx_.view().peek(keylet::account(dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (auto err =
|
||||
verifyDepositPreauth(ctx_.tx, ctx_.view(), txAccount, dst, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
(*slep)[sfBalance] = ctx_.tx[sfBalance];
|
||||
XRPAmount const reqDelta = reqBalance - chanBalance;
|
||||
XRPL_ASSERT(reqDelta >= beast::zero, "xrpl::PayChanClaim::doApply : minimum balance delta");
|
||||
(*sled)[sfBalance] = (*sled)[sfBalance] + reqDelta;
|
||||
ctx_.view().update(sled);
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.getFlags() & tfRenew)
|
||||
{
|
||||
if (src != txAccount)
|
||||
return tecNO_PERMISSION;
|
||||
(*slep)[~sfExpiration] = std::nullopt;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
if (ctx_.tx.getFlags() & tfClose)
|
||||
{
|
||||
// Channel will close immediately if dry or the receiver closes
|
||||
if (dst == txAccount || (*slep)[sfBalance] == (*slep)[sfAmount])
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
|
||||
auto const settleExpiration =
|
||||
ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
(*slep)[sfSettleDelay];
|
||||
|
||||
if (!curExpiration || *curExpiration > settleExpiration)
|
||||
{
|
||||
(*slep)[~sfExpiration] = settleExpiration;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
173
src/libxrpl/tx/transactors/payment_channel/PayChanCreate.cpp
Normal file
173
src/libxrpl/tx/transactors/payment_channel/PayChanCreate.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include <xrpl/basics/chrono.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PayChanCreate.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
/*
|
||||
PaymentChannel
|
||||
|
||||
Payment channels permit off-ledger checkpoints of XRP payments flowing
|
||||
in a single direction. A channel sequesters the owner's XRP in its own
|
||||
ledger entry. The owner can authorize the recipient to claim up to a
|
||||
given balance by giving the receiver a signed message (off-ledger). The
|
||||
recipient can use this signed message to claim any unpaid balance while
|
||||
the channel remains open. The owner can top off the line as needed. If
|
||||
the channel has not paid out all its funds, the owner must wait out a
|
||||
delay to close the channel to give the recipient a chance to supply any
|
||||
claims. The recipient can close the channel at any time. Any transaction
|
||||
that touches the channel after the expiration time will close the
|
||||
channel. The total amount paid increases monotonically as newer claims
|
||||
are issued. When the channel is closed any remaining balance is returned
|
||||
to the owner. Channels are intended to permit intermittent off-ledger
|
||||
settlement of ILP trust lines as balances get substantial. For
|
||||
bidirectional channels, a payment channel can be used in each direction.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TxConsequences
|
||||
PayChanCreate::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PayChanCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfAccount] == ctx.tx[sfDestination])
|
||||
return temDST_IS_SRC;
|
||||
|
||||
if (!publicKeyType(ctx.tx[sfPublicKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const sle = ctx.view.read(keylet::account(account));
|
||||
if (!sle)
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check reserve and funds availability
|
||||
{
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve = ctx.view.fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
auto const dst = ctx.tx[sfDestination];
|
||||
|
||||
{
|
||||
// Check destination account
|
||||
auto const sled = ctx.view.read(keylet::account(dst));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
auto const flags = sled->getFlags();
|
||||
|
||||
// Check if they have disallowed incoming payment channels
|
||||
if (flags & lsfDisallowIncomingPayChan)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if ((flags & lsfRequireDestTag) && !ctx.tx[~sfDestinationTag])
|
||||
return tecDST_TAG_NEEDED;
|
||||
|
||||
// Pseudo-accounts cannot receive payment channels, other than native
|
||||
// to their underlying ledger object - implemented in their respective
|
||||
// transaction types. 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(sled))
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanCreate::doApply()
|
||||
{
|
||||
auto const account = ctx_.tx[sfAccount];
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (ctx_.view().rules().enabled(fixPayChanCancelAfter))
|
||||
{
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime;
|
||||
if (ctx_.tx[~sfCancelAfter] && after(closeTime, ctx_.tx[sfCancelAfter]))
|
||||
return tecEXPIRED;
|
||||
}
|
||||
|
||||
auto const dst = ctx_.tx[sfDestination];
|
||||
|
||||
// Create PayChan in ledger.
|
||||
//
|
||||
// Note that we we use the value from the sequence or ticket as the
|
||||
// payChan sequence. For more explanation see comments in SeqProxy.h.
|
||||
Keylet const payChanKeylet = keylet::payChan(account, dst, ctx_.tx.getSeqValue());
|
||||
auto const slep = std::make_shared<SLE>(payChanKeylet);
|
||||
|
||||
// Funds held in this channel
|
||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||
// Amount channel has already paid
|
||||
(*slep)[sfBalance] = ctx_.tx[sfAmount].zeroed();
|
||||
(*slep)[sfAccount] = account;
|
||||
(*slep)[sfDestination] = dst;
|
||||
(*slep)[sfSettleDelay] = ctx_.tx[sfSettleDelay];
|
||||
(*slep)[sfPublicKey] = ctx_.tx[sfPublicKey];
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
(*slep)[sfSequence] = ctx_.tx.getSeqValue();
|
||||
}
|
||||
|
||||
ctx_.view().insert(slep);
|
||||
|
||||
// Add PayChan to owner directory
|
||||
{
|
||||
auto const page = ctx_.view().dirInsert(
|
||||
keylet::ownerDir(account), payChanKeylet, describeOwnerDir(account));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfOwnerNode] = *page;
|
||||
}
|
||||
|
||||
// Add PayChan to the recipient's owner directory
|
||||
{
|
||||
auto const page =
|
||||
ctx_.view().dirInsert(keylet::ownerDir(dst), payChanKeylet, describeOwnerDir(dst));
|
||||
if (!page)
|
||||
return tecDIR_FULL; // LCOV_EXCL_LINE
|
||||
(*slep)[sfDestinationNode] = *page;
|
||||
}
|
||||
|
||||
// Deduct owner's balance, increment owner count
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
92
src/libxrpl/tx/transactors/payment_channel/PayChanFund.cpp
Normal file
92
src/libxrpl/tx/transactors/payment_channel/PayChanFund.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PayChanFund.h>
|
||||
|
||||
#include <libxrpl/tx/transactors/payment_channel/PayChanHelpers.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TxConsequences
|
||||
PayChanFund::makeTxConsequences(PreflightContext const& ctx)
|
||||
{
|
||||
return TxConsequences{ctx.tx, ctx.tx[sfAmount].xrp()};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
PayChanFund::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!isXRP(ctx.tx[sfAmount]) || (ctx.tx[sfAmount] <= beast::zero))
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
PayChanFund::doApply()
|
||||
{
|
||||
Keylet const k(ltPAYCHAN, ctx_.tx[sfChannel]);
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
return tecNO_ENTRY;
|
||||
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
auto const txAccount = ctx_.tx[sfAccount];
|
||||
auto const expiration = (*slep)[~sfExpiration];
|
||||
|
||||
{
|
||||
auto const cancelAfter = (*slep)[~sfCancelAfter];
|
||||
auto const closeTime = ctx_.view().header().parentCloseTime.time_since_epoch().count();
|
||||
if ((cancelAfter && closeTime >= *cancelAfter) || (expiration && closeTime >= *expiration))
|
||||
return closeChannel(slep, ctx_.view(), k.key, ctx_.registry.journal("View"));
|
||||
}
|
||||
|
||||
if (src != txAccount)
|
||||
// only the owner can add funds or extend
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (auto extend = ctx_.tx[~sfExpiration])
|
||||
{
|
||||
auto minExpiration = ctx_.view().header().parentCloseTime.time_since_epoch().count() +
|
||||
(*slep)[sfSettleDelay];
|
||||
if (expiration && *expiration < minExpiration)
|
||||
minExpiration = *expiration;
|
||||
|
||||
if (*extend < minExpiration)
|
||||
return temBAD_EXPIRATION;
|
||||
(*slep)[~sfExpiration] = *extend;
|
||||
ctx_.view().update(slep);
|
||||
}
|
||||
|
||||
auto const sle = ctx_.view().peek(keylet::account(txAccount));
|
||||
if (!sle)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
{
|
||||
// Check reserve and funds availability
|
||||
auto const balance = (*sle)[sfBalance];
|
||||
auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount]);
|
||||
|
||||
if (balance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
if (balance < reserve + ctx_.tx[sfAmount])
|
||||
return tecUNFUNDED;
|
||||
}
|
||||
|
||||
// do not allow adding funds if dst does not exist
|
||||
if (AccountID const dst = (*slep)[sfDestination]; !ctx_.view().read(keylet::account(dst)))
|
||||
{
|
||||
return tecNO_DST;
|
||||
}
|
||||
|
||||
(*slep)[sfAmount] = (*slep)[sfAmount] + ctx_.tx[sfAmount];
|
||||
ctx_.view().update(slep);
|
||||
|
||||
(*sle)[sfBalance] = (*sle)[sfBalance] - ctx_.tx[sfAmount];
|
||||
ctx_.view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -0,0 +1,58 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
#include <libxrpl/tx/transactors/payment_channel/PayChanHelpers.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TER
|
||||
closeChannel(
|
||||
std::shared_ptr<SLE> const& slep,
|
||||
ApplyView& view,
|
||||
uint256 const& key,
|
||||
beast::Journal j)
|
||||
{
|
||||
AccountID const src = (*slep)[sfAccount];
|
||||
// Remove PayChan from owner directory
|
||||
{
|
||||
auto const page = (*slep)[sfOwnerNode];
|
||||
if (!view.dirRemove(keylet::ownerDir(src), page, key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not remove paychan from src owner directory";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Remove PayChan from recipient's owner directory, if present.
|
||||
if (auto const page = (*slep)[~sfDestinationNode])
|
||||
{
|
||||
auto const dst = (*slep)[sfDestination];
|
||||
if (!view.dirRemove(keylet::ownerDir(dst), *page, key, true))
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
JLOG(j.fatal()) << "Could not remove paychan from dst owner directory";
|
||||
return tefBAD_LEDGER;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer amount back to owner, decrement owner count
|
||||
auto const sle = view.peek(keylet::account(src));
|
||||
if (!sle)
|
||||
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);
|
||||
|
||||
// Remove PayChan from ledger
|
||||
view.erase(slep);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
15
src/libxrpl/tx/transactors/payment_channel/PayChanHelpers.h
Normal file
15
src/libxrpl/tx/transactors/payment_channel/PayChanHelpers.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
TER
|
||||
closeChannel(
|
||||
std::shared_ptr<SLE> const& slep,
|
||||
ApplyView& view,
|
||||
uint256 const& key,
|
||||
beast::Journal j);
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,160 +0,0 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTClawback.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTClawback::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
|
||||
// Only issuer can clawback
|
||||
if (account != MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer())
|
||||
return temMALFORMED;
|
||||
|
||||
// Cannot clawback from self
|
||||
if (account == ctx.tx[sfHolder])
|
||||
return temMALFORMED;
|
||||
|
||||
// Check invalid claw amount
|
||||
auto const clawAmount = ctx.tx[sfMPTAmount];
|
||||
if (clawAmount == 0 || clawAmount > maxMPTokenAmount)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
// Verify proof length
|
||||
if (ctx.tx[sfZKProof].length() != ecEqualityProofLength)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTClawback::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// Check if sender account exists
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
if (!ctx.view.exists(keylet::account(account)))
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check if holder account exists
|
||||
auto const holder = ctx.tx[sfHolder];
|
||||
if (!ctx.view.exists(keylet::account(holder)))
|
||||
return tecNO_TARGET;
|
||||
|
||||
// Check if MPT issuance exists
|
||||
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Sanity check: issuer must be the same as account
|
||||
if (sleIssuance->getAccountID(sfIssuer) != account)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Check if issuance has issuer ElGamal public key
|
||||
if (!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check if clawback is allowed
|
||||
if (!sleIssuance->isFlag(lsfMPTCanClawback))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check holder's MPToken
|
||||
auto const sleHolderMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, holder));
|
||||
if (!sleHolderMPToken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Check if holder has confidential balances to claw back
|
||||
if (!sleHolderMPToken->isFieldPresent(sfIssuerEncryptedBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Sanity check: claw amount can not exceed confidential outstanding amount
|
||||
auto const amount = ctx.tx[sfMPTAmount];
|
||||
if (amount > (*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0))
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
|
||||
auto const contextHash =
|
||||
getClawbackContextHash(account, mptIssuanceID, ctx.tx.getSeqProxy().value(), holder);
|
||||
|
||||
// Verify the revealed confidential amount by the issuer matches the exact
|
||||
// confidential balance of the holder.
|
||||
return verifyClawbackEqualityProof(
|
||||
amount,
|
||||
ctx.tx[sfZKProof],
|
||||
(*sleIssuance)[sfIssuerEncryptionKey],
|
||||
(*sleHolderMPToken)[sfIssuerEncryptedBalance],
|
||||
contextHash);
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTClawback::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto const holder = ctx_.tx[sfHolder];
|
||||
|
||||
auto sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||
auto sleHolderMPToken = view().peek(keylet::mptoken(mptIssuanceID, holder));
|
||||
|
||||
if (!sleIssuance || !sleHolderMPToken)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const clawAmount = ctx_.tx[sfMPTAmount];
|
||||
|
||||
Slice const holderPubKey = (*sleHolderMPToken)[sfHolderEncryptionKey];
|
||||
Slice const issuerPubKey = (*sleIssuance)[sfIssuerEncryptionKey];
|
||||
|
||||
// After clawback, the balance should be encrypted zero.
|
||||
auto const encZeroForHolder = encryptCanonicalZeroAmount(holderPubKey, holder, mptIssuanceID);
|
||||
if (!encZeroForHolder)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto encZeroForIssuer = encryptCanonicalZeroAmount(issuerPubKey, holder, mptIssuanceID);
|
||||
if (!encZeroForIssuer)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Set holder's confidential balances to encrypted zero
|
||||
(*sleHolderMPToken)[sfConfidentialBalanceInbox] = *encZeroForHolder;
|
||||
(*sleHolderMPToken)[sfConfidentialBalanceSpending] = *encZeroForHolder;
|
||||
(*sleHolderMPToken)[sfIssuerEncryptedBalance] = std::move(*encZeroForIssuer);
|
||||
incrementConfidentialVersion(*sleHolderMPToken);
|
||||
|
||||
if (sleHolderMPToken->isFieldPresent(sfAuditorEncryptedBalance))
|
||||
{
|
||||
// Sanity check: the issuance must have an auditor public key if
|
||||
// auditing is enabled.
|
||||
if (!sleIssuance->isFieldPresent(sfAuditorEncryptionKey))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
Slice const auditorPubKey = (*sleIssuance)[sfAuditorEncryptionKey];
|
||||
|
||||
auto encZeroForAuditor = encryptCanonicalZeroAmount(auditorPubKey, holder, mptIssuanceID);
|
||||
|
||||
if (!encZeroForAuditor)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleHolderMPToken)[sfAuditorEncryptedBalance] = std::move(*encZeroForAuditor);
|
||||
}
|
||||
|
||||
// Decrease Global Confidential Outstanding Amount
|
||||
auto const oldCOA = (*sleIssuance)[sfConfidentialOutstandingAmount];
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] = oldCOA - clawAmount;
|
||||
|
||||
// Decrease Global Total Outstanding Amount
|
||||
auto const oldOA = (*sleIssuance)[sfOutstandingAmount];
|
||||
(*sleIssuance)[sfOutstandingAmount] = oldOA - clawAmount;
|
||||
|
||||
view().update(sleHolderMPToken);
|
||||
view().update(sleIssuance);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
} // namespace xrpl
|
||||
@@ -1,253 +0,0 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTConvert.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTConvert::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
// issuer cannot convert
|
||||
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.tx[sfMPTAmount] > maxMPTokenAmount)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength)
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfHolderEncryptionKey))
|
||||
{
|
||||
if (!isValidCompressedECPoint(ctx.tx[sfHolderEncryptionKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
// proof of knowledge of the secret key corresponding to the provided
|
||||
// public key is needed when holder ec public key is being set.
|
||||
if (!ctx.tx.isFieldPresent(sfZKProof))
|
||||
return temMALFORMED;
|
||||
|
||||
// verify schnorr proof length when registerring holder ec public key
|
||||
if (ctx.tx[sfZKProof].size() != ecSchnorrProofLength)
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
// zkp should not be present if public key was already set
|
||||
if (ctx.tx.isFieldPresent(sfZKProof))
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// check encrypted amount format after the above basic checks
|
||||
// this check is more expensive so put it at the end
|
||||
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
|
||||
return res;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTConvert::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const issuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const amount = ctx.tx[sfMPTAmount];
|
||||
|
||||
// ensure that issuance exists
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(issuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// already checked in preflight, but should also check that issuer on the
|
||||
// issuance isn't the account either
|
||||
if (sleIssuance->getAccountID(sfIssuer) == account)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// issuer has not uploaded their pub key yet
|
||||
if (!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
|
||||
|
||||
// tx must include auditor ciphertext if the issuance has enabled
|
||||
// auditing, and must not include it if auditing is not enabled
|
||||
if (requiresAuditor != hasAuditor)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
auto const sleMptoken = ctx.view.read(keylet::mptoken(issuanceID, account));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto const mptIssue = MPTIssue{issuanceID};
|
||||
STAmount const mptAmount =
|
||||
STAmount(MPTAmount{static_cast<MPTAmount::value_type>(amount)}, mptIssue);
|
||||
if (accountHolds(
|
||||
ctx.view,
|
||||
account,
|
||||
mptIssue,
|
||||
FreezeHandling::fhZERO_IF_FROZEN,
|
||||
AuthHandling::ahZERO_IF_UNAUTHORIZED,
|
||||
ctx.j) < mptAmount)
|
||||
{
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
auto const hasHolderKeyOnLedger = sleMptoken->isFieldPresent(sfHolderEncryptionKey);
|
||||
auto const hasHolderKeyInTx = ctx.tx.isFieldPresent(sfHolderEncryptionKey);
|
||||
|
||||
// must have pk to convert
|
||||
if (!hasHolderKeyOnLedger && !hasHolderKeyInTx)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// can't update if there's already a pk
|
||||
if (hasHolderKeyOnLedger && hasHolderKeyInTx)
|
||||
return tecDUPLICATE;
|
||||
|
||||
Slice holderPubKey;
|
||||
if (hasHolderKeyInTx)
|
||||
{
|
||||
holderPubKey = ctx.tx[sfHolderEncryptionKey];
|
||||
|
||||
auto const contextHash =
|
||||
getConvertContextHash(account, issuanceID, ctx.tx.getSeqProxy().value());
|
||||
|
||||
// when register new pk, verify through schnorr proof
|
||||
if (!isTesSuccess(verifySchnorrProof(holderPubKey, ctx.tx[sfZKProof], contextHash)))
|
||||
{
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
holderPubKey = (*sleMptoken)[sfHolderEncryptionKey];
|
||||
}
|
||||
|
||||
std::optional<ConfidentialRecipient> auditor;
|
||||
if (hasAuditor)
|
||||
{
|
||||
auditor.emplace(
|
||||
ConfidentialRecipient{
|
||||
(*sleIssuance)[sfAuditorEncryptionKey], ctx.tx[sfAuditorEncryptedAmount]});
|
||||
}
|
||||
|
||||
return verifyRevealedAmount(
|
||||
amount,
|
||||
ctx.tx[sfBlindingFactor],
|
||||
{holderPubKey, ctx.tx[sfHolderEncryptedAmount]},
|
||||
{(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]},
|
||||
auditor);
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTConvert::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
||||
if (!sleMptoken)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const amtToConvert = ctx_.tx[sfMPTAmount];
|
||||
auto const amt = (*sleMptoken)[~sfMPTAmount].value_or(0);
|
||||
|
||||
if (ctx_.tx.isFieldPresent(sfHolderEncryptionKey))
|
||||
(*sleMptoken)[sfHolderEncryptionKey] = ctx_.tx[sfHolderEncryptionKey];
|
||||
|
||||
// Converting decreases regular balance and increases confidential outstanding.
|
||||
// The confidential outstanding tracks total tokens in confidential form globally.
|
||||
(*sleMptoken)[sfMPTAmount] = amt - amtToConvert;
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] =
|
||||
(*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) + amtToConvert;
|
||||
|
||||
Slice const holderEc = ctx_.tx[sfHolderEncryptedAmount];
|
||||
Slice const issuerEc = ctx_.tx[sfIssuerEncryptedAmount];
|
||||
|
||||
auto const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
|
||||
|
||||
// Two cases for Convert:
|
||||
// 1. Holder already has confidential balances -> homomorphically add to inbox
|
||||
// 2. First-time convert -> initialize all confidential balance fields
|
||||
if (sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
|
||||
sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
|
||||
sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
{
|
||||
// Case 1: Add to existing inbox balance (holder will merge later)
|
||||
{
|
||||
auto sum = homomorphicAdd(holderEc, (*sleMptoken)[sfConfidentialBalanceInbox]);
|
||||
if (!sum)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfConfidentialBalanceInbox] = std::move(*sum);
|
||||
}
|
||||
|
||||
// homomorphically add issuer's encrypted balance
|
||||
{
|
||||
auto sum = homomorphicAdd(issuerEc, (*sleMptoken)[sfIssuerEncryptedBalance]);
|
||||
if (!sum)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfIssuerEncryptedBalance] = std::move(*sum);
|
||||
}
|
||||
|
||||
// homomorphically add auditor's encrypted balance
|
||||
if (auditorEc)
|
||||
{
|
||||
auto sum = homomorphicAdd(*auditorEc, (*sleMptoken)[sfAuditorEncryptedBalance]);
|
||||
if (!sum)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance] = std::move(*sum);
|
||||
}
|
||||
}
|
||||
else if (
|
||||
!sleMptoken->isFieldPresent(sfIssuerEncryptedBalance) &&
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) &&
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
{
|
||||
// Case 2: First-time convert - initialize all confidential fields
|
||||
(*sleMptoken)[sfConfidentialBalanceInbox] = holderEc;
|
||||
(*sleMptoken)[sfIssuerEncryptedBalance] = issuerEc;
|
||||
(*sleMptoken)[sfConfidentialBalanceVersion] = 0;
|
||||
|
||||
if (auditorEc)
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance] = *auditorEc;
|
||||
|
||||
// Spending balance starts at zero. Must use canonical zero encryption
|
||||
// (deterministic ciphertext) so the ledger state is reproducible.
|
||||
auto zeroBalance = encryptCanonicalZeroAmount(
|
||||
(*sleMptoken)[sfHolderEncryptionKey], account_, mptIssuanceID);
|
||||
|
||||
if (!zeroBalance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*zeroBalance);
|
||||
}
|
||||
else
|
||||
{
|
||||
// both sfIssuerEncryptedBalance and sfConfidentialBalanceInbox should
|
||||
// exist together
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
view().update(sleIssuance);
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,294 +0,0 @@
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTConvertBack.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTConvertBack::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
// issuer cannot convert back
|
||||
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.tx[sfMPTAmount] == 0 || ctx.tx[sfMPTAmount] > maxMPTokenAmount)
|
||||
return temBAD_AMOUNT;
|
||||
|
||||
if (ctx.tx[sfBlindingFactor].size() != ecBlindingFactorLength)
|
||||
return temMALFORMED;
|
||||
|
||||
if (!isValidCompressedECPoint(ctx.tx[sfBalanceCommitment]))
|
||||
return temMALFORMED;
|
||||
|
||||
// check encrypted amount format after the above basic checks
|
||||
// this check is more expensive so put it at the end
|
||||
if (auto const res = checkEncryptedAmountFormat(ctx.tx); !isTesSuccess(res))
|
||||
return res;
|
||||
|
||||
// ConvertBack proof = pedersen linkage proof + single bulletproof
|
||||
if (ctx.tx[sfZKProof].size() != ecPedersenProofLength + ecSingleBulletproofLength)
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the cryptographic proofs for a ConvertBack transaction.
|
||||
*
|
||||
* This function verifies three proofs:
|
||||
* 1. Revealed amount proof: verifies the encrypted amounts (holder, issuer,
|
||||
* auditor) all encrypt the same revealed amount using the blinding factor.
|
||||
* 2. Pedersen linkage proof: verifies the balance commitment is derived from
|
||||
* the holder's encrypted spending balance.
|
||||
* 3. Bulletproof (range proof): verifies the remaining balance (balance - amount)
|
||||
* is non-negative, preventing overdrafts.
|
||||
*
|
||||
* All proofs are verified before returning any error to prevent timing attacks.
|
||||
*/
|
||||
static TER
|
||||
verifyProofs(
|
||||
STTx const& tx,
|
||||
std::shared_ptr<SLE const> const& issuance,
|
||||
std::shared_ptr<SLE const> const& mptoken)
|
||||
{
|
||||
if (!mptoken->isFieldPresent(sfHolderEncryptionKey))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const mptIssuanceID = tx[sfMPTokenIssuanceID];
|
||||
auto const account = tx[sfAccount];
|
||||
auto const amount = tx[sfMPTAmount];
|
||||
auto const blindingFactor = tx[sfBlindingFactor];
|
||||
auto const holderPubKey = (*mptoken)[sfHolderEncryptionKey];
|
||||
|
||||
auto const contextHash = getConvertBackContextHash(
|
||||
account,
|
||||
mptIssuanceID,
|
||||
tx.getSeqProxy().value(),
|
||||
(*mptoken)[~sfConfidentialBalanceVersion].value_or(0));
|
||||
|
||||
// Prepare Auditor Info
|
||||
std::optional<ConfidentialRecipient> auditor;
|
||||
bool const hasAuditor = issuance->isFieldPresent(sfAuditorEncryptionKey);
|
||||
if (hasAuditor)
|
||||
{
|
||||
auditor.emplace(
|
||||
ConfidentialRecipient{
|
||||
(*issuance)[sfAuditorEncryptionKey], tx[sfAuditorEncryptedAmount]});
|
||||
}
|
||||
|
||||
// Run all verifications before returning any error to prevent timing attacks
|
||||
// that could reveal which proof failed.
|
||||
bool valid = true;
|
||||
|
||||
// verify revealed amount
|
||||
if (auto const ter = verifyRevealedAmount(
|
||||
amount,
|
||||
blindingFactor,
|
||||
{holderPubKey, tx[sfHolderEncryptedAmount]},
|
||||
{(*issuance)[sfIssuerEncryptionKey], tx[sfIssuerEncryptedAmount]},
|
||||
auditor);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Parse proof components using offset
|
||||
auto const proof = tx[sfZKProof];
|
||||
size_t remainingLength = proof.size();
|
||||
size_t currentOffset = 0;
|
||||
|
||||
// Extract Pedersen linkage proof
|
||||
if (remainingLength < ecPedersenProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const pedersenProof = proof.substr(currentOffset, ecPedersenProofLength);
|
||||
currentOffset += ecPedersenProofLength;
|
||||
remainingLength -= ecPedersenProofLength;
|
||||
|
||||
// Extract bulletproof
|
||||
if (remainingLength < ecSingleBulletproofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const bulletproof = proof.substr(currentOffset, ecSingleBulletproofLength);
|
||||
currentOffset += ecSingleBulletproofLength;
|
||||
remainingLength -= ecSingleBulletproofLength;
|
||||
|
||||
if (remainingLength != 0)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// verify el gamal pedersen linkage
|
||||
if (auto const ter = verifyBalancePcmLinkage(
|
||||
pedersenProof,
|
||||
(*mptoken)[sfConfidentialBalanceSpending],
|
||||
holderPubKey,
|
||||
tx[sfBalanceCommitment],
|
||||
contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// verify bullet proof
|
||||
{
|
||||
// Compute PC_rem = PC_balance - mG (the commitment to the remaining balance)
|
||||
Buffer pcRem;
|
||||
if (auto const ter = computeConvertBackRemainder(tx[sfBalanceCommitment], amount, pcRem);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// The bulletproof verifies that the remaining balance is non-negative
|
||||
std::vector<Slice> commitments{Slice(pcRem.data(), pcRem.size())};
|
||||
|
||||
if (auto const ter = verifyAggregatedBulletproof(bulletproof, commitments, contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
return tecBAD_PROOF;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTConvertBack::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const amount = ctx.tx[sfMPTAmount];
|
||||
|
||||
// ensure that issuance exists
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
|
||||
|
||||
// tx must include auditor ciphertext if the issuance has enabled
|
||||
// auditing
|
||||
if (requiresAuditor && !hasAuditor)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// if auditing is not supported then user should not upload auditor
|
||||
// ciphertext
|
||||
if (!requiresAuditor && hasAuditor)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// already checked in preflight, but should also check that issuer on
|
||||
// the issuance isn't the account either
|
||||
if (sleIssuance->getAccountID(sfIssuer) == account)
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMptoken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
!sleMptoken->isFieldPresent(sfHolderEncryptionKey))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// if the total circulating confidential balance is smaller than what the
|
||||
// holder is trying to convert back, we know for sure this txn should
|
||||
// fail
|
||||
if ((*sleIssuance)[~sfConfidentialOutstandingAmount].value_or(0) < amount)
|
||||
{
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// Check lock
|
||||
MPTIssue const mptIssue(mptIssuanceID);
|
||||
if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Check auth
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (TER const res = verifyProofs(ctx.tx, sleIssuance, sleMptoken); !isTesSuccess(res))
|
||||
return res;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTConvertBack::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
||||
if (!sleMptoken)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto sleIssuance = view().peek(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const amtToConvertBack = ctx_.tx[sfMPTAmount];
|
||||
auto const amt = (*sleMptoken)[~sfMPTAmount].value_or(0);
|
||||
|
||||
// Converting back increases regular balance and decreases confidential
|
||||
// outstanding. This is the inverse of Convert.
|
||||
(*sleMptoken)[sfMPTAmount] = amt + amtToConvertBack;
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] =
|
||||
(*sleIssuance)[sfConfidentialOutstandingAmount] - amtToConvertBack;
|
||||
|
||||
std::optional<Slice> const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
|
||||
|
||||
// homomorphically subtract holder's encrypted balance
|
||||
{
|
||||
auto res = homomorphicSubtract(
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending], ctx_.tx[sfHolderEncryptedAmount]);
|
||||
if (!res)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*res);
|
||||
}
|
||||
|
||||
// homomorphically subtract issuer's encrypted balance
|
||||
{
|
||||
auto res = homomorphicSubtract(
|
||||
(*sleMptoken)[sfIssuerEncryptedBalance], ctx_.tx[sfIssuerEncryptedAmount]);
|
||||
if (!res)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfIssuerEncryptedBalance] = std::move(*res);
|
||||
}
|
||||
|
||||
if (auditorEc)
|
||||
{
|
||||
auto res = homomorphicSubtract(
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance], ctx_.tx[sfAuditorEncryptedAmount]);
|
||||
if (!res)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfAuditorEncryptedBalance] = std::move(*res);
|
||||
}
|
||||
|
||||
incrementConfidentialVersion(*sleMptoken);
|
||||
|
||||
view().update(sleIssuance);
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,94 +0,0 @@
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTMergeInbox.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTMergeInbox::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
// issuer cannot merge
|
||||
if (MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer() == ctx.tx[sfAccount])
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTMergeInbox::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// already checked in preflight, but should also check that issuer on the
|
||||
// issuance isn't the account either
|
||||
if (sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount])
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const sleMptoken =
|
||||
ctx.view.read(keylet::mptoken(ctx.tx[sfMPTokenIssuanceID], ctx.tx[sfAccount]));
|
||||
if (!sleMptoken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
!sleMptoken->isFieldPresent(sfHolderEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTMergeInbox::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto sleMptoken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
||||
if (!sleMptoken)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// sanity check
|
||||
if (!sleMptoken->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
!sleMptoken->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
!sleMptoken->isFieldPresent(sfHolderEncryptionKey))
|
||||
{
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
// Merge inbox into spending: spending = spending + inbox
|
||||
// This allows holder to use received funds. Without merging, incoming
|
||||
// transfers sit in inbox and cannot be spent or converted back.
|
||||
auto sum = homomorphicAdd(
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending], (*sleMptoken)[sfConfidentialBalanceInbox]);
|
||||
if (!sum)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfConfidentialBalanceSpending] = std::move(*sum);
|
||||
|
||||
// Reset inbox to encrypted zero. Must use canonical zero encryption
|
||||
// (deterministic ciphertext) so the ledger state is reproducible.
|
||||
auto zeroEncryption =
|
||||
encryptCanonicalZeroAmount((*sleMptoken)[sfHolderEncryptionKey], account_, mptIssuanceID);
|
||||
|
||||
if (!zeroEncryption)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleMptoken)[sfConfidentialBalanceInbox] = std::move(*zeroEncryption);
|
||||
|
||||
incrementConfidentialVersion(*sleMptoken);
|
||||
|
||||
view().update(sleMptoken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,407 +0,0 @@
|
||||
#include <xrpl/ledger/CredentialHelpers.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/token/ConfidentialMPTSend.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
NotTEC
|
||||
ConfidentialMPTSend::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
auto const issuer = MPTIssue(ctx.tx[sfMPTokenIssuanceID]).getIssuer();
|
||||
|
||||
// ConfidentialMPTSend only allows holder to holder, holder to second account,
|
||||
// and second account to holder transfers. So issuer cannot be the sender.
|
||||
if (account == issuer)
|
||||
return temMALFORMED;
|
||||
|
||||
// Can not send to self
|
||||
if (account == ctx.tx[sfDestination])
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the length of the encrypted amounts
|
||||
if (ctx.tx[sfSenderEncryptedAmount].length() != ecGamalEncryptedTotalLength ||
|
||||
ctx.tx[sfDestinationEncryptedAmount].length() != ecGamalEncryptedTotalLength ||
|
||||
ctx.tx[sfIssuerEncryptedAmount].length() != ecGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
if (hasAuditor && ctx.tx[sfAuditorEncryptedAmount].length() != ecGamalEncryptedTotalLength)
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
// Check the length of the ZKProof
|
||||
auto const recipientCount = getConfidentialRecipientCount(hasAuditor);
|
||||
auto const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(recipientCount);
|
||||
auto const sizePedersenLinkage = 2 * ecPedersenProofLength;
|
||||
|
||||
if (ctx.tx[sfZKProof].length() !=
|
||||
sizeEquality + sizePedersenLinkage + ecDoubleBulletproofLength)
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the Pedersen commitments are valid
|
||||
if (!isValidCompressedECPoint(ctx.tx[sfBalanceCommitment]) ||
|
||||
!isValidCompressedECPoint(ctx.tx[sfAmountCommitment]))
|
||||
return temMALFORMED;
|
||||
|
||||
// Check the encrypted amount formats, this is more expensive so put it at
|
||||
// the end
|
||||
if (!isValidCiphertext(ctx.tx[sfSenderEncryptedAmount]) ||
|
||||
!isValidCiphertext(ctx.tx[sfDestinationEncryptedAmount]) ||
|
||||
!isValidCiphertext(ctx.tx[sfIssuerEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (hasAuditor && !isValidCiphertext(ctx.tx[sfAuditorEncryptedAmount]))
|
||||
return temBAD_CIPHERTEXT;
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
verifySendProofs(
|
||||
PreclaimContext const& ctx,
|
||||
std::shared_ptr<SLE const> const& sleSenderMPToken,
|
||||
std::shared_ptr<SLE const> const& sleDestinationMPToken,
|
||||
std::shared_ptr<SLE const> const& sleIssuance)
|
||||
{
|
||||
// Sanity check
|
||||
if (!sleSenderMPToken || !sleDestinationMPToken || !sleIssuance)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
auto const recipientCount = getConfidentialRecipientCount(hasAuditor);
|
||||
auto const proof = ctx.tx[sfZKProof];
|
||||
size_t remainingLength = proof.size();
|
||||
size_t currentOffset = 0;
|
||||
|
||||
// Extract equality proof
|
||||
auto const sizeEquality = secp256k1_mpt_proof_equality_shared_r_size(recipientCount);
|
||||
if (remainingLength < sizeEquality)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const equalityProof = proof.substr(currentOffset, sizeEquality);
|
||||
currentOffset += sizeEquality;
|
||||
remainingLength -= sizeEquality;
|
||||
|
||||
// Extract Pedersen linkage proof for amount commitment
|
||||
if (remainingLength < ecPedersenProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const amountLinkageProof = proof.substr(currentOffset, ecPedersenProofLength);
|
||||
currentOffset += ecPedersenProofLength;
|
||||
remainingLength -= ecPedersenProofLength;
|
||||
|
||||
// Extract Pedersen linkage proof for balance commitment
|
||||
if (remainingLength < ecPedersenProofLength)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
auto const balanceLinkageProof = proof.substr(currentOffset, ecPedersenProofLength);
|
||||
currentOffset += ecPedersenProofLength;
|
||||
remainingLength -= ecPedersenProofLength;
|
||||
|
||||
// Extract range proof
|
||||
if (remainingLength < ecDoubleBulletproofLength)
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const rangeProof = proof.substr(currentOffset, ecDoubleBulletproofLength);
|
||||
currentOffset += ecDoubleBulletproofLength;
|
||||
remainingLength -= ecDoubleBulletproofLength;
|
||||
|
||||
if (remainingLength != 0)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Prepare recipient list
|
||||
std::vector<ConfidentialRecipient> recipients;
|
||||
recipients.reserve(recipientCount);
|
||||
|
||||
recipients.push_back(
|
||||
{(*sleSenderMPToken)[sfHolderEncryptionKey], ctx.tx[sfSenderEncryptedAmount]});
|
||||
recipients.push_back(
|
||||
{(*sleDestinationMPToken)[sfHolderEncryptionKey], ctx.tx[sfDestinationEncryptedAmount]});
|
||||
recipients.push_back({(*sleIssuance)[sfIssuerEncryptionKey], ctx.tx[sfIssuerEncryptedAmount]});
|
||||
|
||||
if (hasAuditor)
|
||||
{
|
||||
recipients.push_back(
|
||||
{(*sleIssuance)[sfAuditorEncryptionKey], ctx.tx[sfAuditorEncryptedAmount]});
|
||||
}
|
||||
|
||||
// Prepare the context hash
|
||||
auto const contextHash = getSendContextHash(
|
||||
ctx.tx[sfAccount],
|
||||
ctx.tx[sfMPTokenIssuanceID],
|
||||
ctx.tx.getSeqProxy().value(),
|
||||
ctx.tx[sfDestination],
|
||||
(*sleSenderMPToken)[~sfConfidentialBalanceVersion].value_or(0));
|
||||
|
||||
// Use a boolean flag to track validity instead of returning early on failure to prevent leaking
|
||||
// information about which proof failed through timing differences
|
||||
bool valid = true;
|
||||
|
||||
// Verify the multi-ciphertext equality proof
|
||||
if (auto const ter = verifyMultiCiphertextEqualityProof(
|
||||
equalityProof, recipients, recipientCount, contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Verify amount linkage
|
||||
if (auto const ter = verifyAmountPcmLinkage(
|
||||
amountLinkageProof,
|
||||
ctx.tx[sfSenderEncryptedAmount],
|
||||
(*sleSenderMPToken)[sfHolderEncryptionKey],
|
||||
ctx.tx[sfAmountCommitment],
|
||||
contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Verify balance linkage
|
||||
if (auto const ter = verifyBalancePcmLinkage(
|
||||
balanceLinkageProof,
|
||||
(*sleSenderMPToken)[sfConfidentialBalanceSpending],
|
||||
(*sleSenderMPToken)[sfHolderEncryptionKey],
|
||||
ctx.tx[sfBalanceCommitment],
|
||||
contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// Verify Range Proof
|
||||
{
|
||||
Buffer pcRem;
|
||||
|
||||
// Derive PC_rem = PC_balance - PC_amount
|
||||
if (auto const ter = computeSendRemainder(
|
||||
ctx.tx[sfBalanceCommitment], ctx.tx[sfAmountCommitment], pcRem);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Aggregated commitments: [PC_amount, PC_rem]
|
||||
// Prove that both the transfer amount and the remaining balance are in range
|
||||
std::vector<Slice> commitments;
|
||||
commitments.push_back(ctx.tx[sfAmountCommitment]);
|
||||
commitments.push_back(Slice{pcRem.data(), pcRem.size()});
|
||||
|
||||
if (auto const ter = verifyAggregatedBulletproof(rangeProof, commitments, contextHash);
|
||||
!isTesSuccess(ter))
|
||||
{
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "ConfidentialMPTSend: One or more cryptographic proofs failed.";
|
||||
return tecBAD_PROOF;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTSend::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
// Check if sender account exists
|
||||
auto const account = ctx.tx[sfAccount];
|
||||
if (!ctx.view.exists(keylet::account(account)))
|
||||
return terNO_ACCOUNT;
|
||||
|
||||
// Check if destination account exists
|
||||
auto const destination = ctx.tx[sfDestination];
|
||||
if (!ctx.view.exists(keylet::account(destination)))
|
||||
return tecNO_TARGET;
|
||||
|
||||
// Check if MPT issuance exists
|
||||
auto const mptIssuanceID = ctx.tx[sfMPTokenIssuanceID];
|
||||
auto const sleIssuance = ctx.view.read(keylet::mptIssuance(mptIssuanceID));
|
||||
if (!sleIssuance)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Check if the issuance allows transfer
|
||||
if (!sleIssuance->isFlag(lsfMPTCanTransfer))
|
||||
return tecNO_AUTH;
|
||||
|
||||
// Check if issuance allows confidential transfer
|
||||
if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check if issuance has issuer ElGamal public key
|
||||
if (!sleIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
bool const hasAuditor = ctx.tx.isFieldPresent(sfAuditorEncryptedAmount);
|
||||
bool const requiresAuditor = sleIssuance->isFieldPresent(sfAuditorEncryptionKey);
|
||||
|
||||
// Tx must include auditor ciphertext if the issuance has enabled
|
||||
// auditing, and must not include it if auditing is not enabled
|
||||
if (requiresAuditor != hasAuditor)
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Sanity check: issuer isn't the sender
|
||||
if (sleIssuance->getAccountID(sfIssuer) == ctx.tx[sfAccount])
|
||||
return tefINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
// Check sender's MPToken existence
|
||||
auto const sleSenderMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, account));
|
||||
if (!sleSenderMPToken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Check sender's MPToken has necessary fields for confidential send
|
||||
if (!sleSenderMPToken->isFieldPresent(sfHolderEncryptionKey) ||
|
||||
!sleSenderMPToken->isFieldPresent(sfConfidentialBalanceSpending) ||
|
||||
!sleSenderMPToken->isFieldPresent(sfIssuerEncryptedBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Sanity check: MPToken's auditor field must be present if auditing is
|
||||
// enabled
|
||||
if (requiresAuditor && !sleSenderMPToken->isFieldPresent(sfAuditorEncryptedBalance))
|
||||
return tefINTERNAL;
|
||||
|
||||
// Check destination's MPToken existence
|
||||
auto const sleDestinationMPToken = ctx.view.read(keylet::mptoken(mptIssuanceID, destination));
|
||||
if (!sleDestinationMPToken)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Check destination's MPToken has necessary fields for confidential send
|
||||
if (!sleDestinationMPToken->isFieldPresent(sfHolderEncryptionKey) ||
|
||||
!sleDestinationMPToken->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
!sleDestinationMPToken->isFieldPresent(sfIssuerEncryptedBalance))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Check lock
|
||||
MPTIssue const mptIssue(mptIssuanceID);
|
||||
if (auto const ter = checkFrozen(ctx.view, account, mptIssue); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = checkFrozen(ctx.view, destination, mptIssue); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
// Check auth
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, account); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const ter = requireAuth(ctx.view, mptIssue, destination); !isTesSuccess(ter))
|
||||
return ter;
|
||||
|
||||
if (auto const err = credentials::valid(ctx.tx, ctx.view, ctx.tx[sfAccount], ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
return verifySendProofs(ctx, sleSenderMPToken, sleDestinationMPToken, sleIssuance);
|
||||
}
|
||||
|
||||
TER
|
||||
ConfidentialMPTSend::doApply()
|
||||
{
|
||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||
auto const destination = ctx_.tx[sfDestination];
|
||||
|
||||
auto sleSenderMPToken = view().peek(keylet::mptoken(mptIssuanceID, account_));
|
||||
auto sleDestinationMPToken = view().peek(keylet::mptoken(mptIssuanceID, destination));
|
||||
|
||||
auto sleDestAcct = view().peek(keylet::account(destination));
|
||||
|
||||
if (!sleSenderMPToken || !sleDestinationMPToken || !sleDestAcct)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destination, sleDestAcct, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
Slice const senderEc = ctx_.tx[sfSenderEncryptedAmount];
|
||||
Slice const destEc = ctx_.tx[sfDestinationEncryptedAmount];
|
||||
Slice const issuerEc = ctx_.tx[sfIssuerEncryptedAmount];
|
||||
|
||||
auto const auditorEc = ctx_.tx[~sfAuditorEncryptedAmount];
|
||||
|
||||
// Subtract from sender's spending balance
|
||||
{
|
||||
Slice const curSpending = (*sleSenderMPToken)[sfConfidentialBalanceSpending];
|
||||
auto newSpending = homomorphicSubtract(curSpending, senderEc);
|
||||
if (!newSpending)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleSenderMPToken)[sfConfidentialBalanceSpending] = std::move(*newSpending);
|
||||
}
|
||||
|
||||
// Subtract from issuer's balance
|
||||
{
|
||||
Slice const curIssuerEnc = (*sleSenderMPToken)[sfIssuerEncryptedBalance];
|
||||
auto newIssuerEnc = homomorphicSubtract(curIssuerEnc, issuerEc);
|
||||
if (!newIssuerEnc)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleSenderMPToken)[sfIssuerEncryptedBalance] = std::move(*newIssuerEnc);
|
||||
}
|
||||
|
||||
// Subtract from auditor's balance if present
|
||||
if (auditorEc)
|
||||
{
|
||||
Slice const curAuditorEnc = (*sleSenderMPToken)[sfAuditorEncryptedBalance];
|
||||
auto newAuditorEnc = homomorphicSubtract(curAuditorEnc, *auditorEc);
|
||||
if (!newAuditorEnc)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleSenderMPToken)[sfAuditorEncryptedBalance] = std::move(*newAuditorEnc);
|
||||
}
|
||||
|
||||
// Add to destination's inbox balance
|
||||
{
|
||||
Slice const curInbox = (*sleDestinationMPToken)[sfConfidentialBalanceInbox];
|
||||
auto newInbox = homomorphicAdd(curInbox, destEc);
|
||||
if (!newInbox)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleDestinationMPToken)[sfConfidentialBalanceInbox] = std::move(*newInbox);
|
||||
}
|
||||
|
||||
// Add to issuer's balance
|
||||
{
|
||||
Slice const curIssuerEnc = (*sleDestinationMPToken)[sfIssuerEncryptedBalance];
|
||||
auto newIssuerEnc = homomorphicAdd(curIssuerEnc, issuerEc);
|
||||
if (!newIssuerEnc)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleDestinationMPToken)[sfIssuerEncryptedBalance] = std::move(*newIssuerEnc);
|
||||
}
|
||||
|
||||
// Add to auditor's balance if present
|
||||
if (auditorEc)
|
||||
{
|
||||
Slice const curAuditorEnc = (*sleDestinationMPToken)[sfAuditorEncryptedBalance];
|
||||
auto newAuditorEnc = homomorphicAdd(curAuditorEnc, *auditorEc);
|
||||
if (!newAuditorEnc)
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
(*sleDestinationMPToken)[sfAuditorEncryptedBalance] = std::move(*newAuditorEnc);
|
||||
}
|
||||
|
||||
// increment sender version only; receiver version is not modified by incoming sends
|
||||
incrementConfidentialVersion(*sleSenderMPToken);
|
||||
|
||||
view().update(sleSenderMPToken);
|
||||
view().update(sleDestinationMPToken);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -72,25 +72,6 @@ MPTokenAuthorize::preclaim(PreclaimContext const& ctx)
|
||||
if (ctx.view.rules().enabled(featureSingleAssetVault) && sleMpt->isFlag(lsfMPTLocked))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if (ctx.view.rules().enabled(featureConfidentialTransfer))
|
||||
{
|
||||
auto const sleMptIssuance =
|
||||
ctx.view.read(keylet::mptIssuance(ctx.tx[sfMPTokenIssuanceID]));
|
||||
|
||||
// if there still existing encrypted balances of MPT in
|
||||
// circulation
|
||||
if (sleMptIssuance &&
|
||||
(*sleMptIssuance)[~sfConfidentialOutstandingAmount].value_or(0) != 0)
|
||||
{
|
||||
// this MPT still has encrypted balance, since we don't know
|
||||
// if it's non-zero or not, we won't allow deletion of
|
||||
// MPToken
|
||||
if (sleMpt->isFieldPresent(sfConfidentialBalanceInbox) ||
|
||||
sleMpt->isFieldPresent(sfConfidentialBalanceSpending))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,6 @@ MPTokenIssuanceCreate::checkExtraFeatures(PreflightContext const& ctx)
|
||||
if (ctx.tx.isFieldPresent(sfMutableFlags) && !ctx.rules.enabled(featureDynamicMPT))
|
||||
return false;
|
||||
|
||||
if (ctx.tx.isFlag(tfMPTCanConfidentialAmount) &&
|
||||
!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return false;
|
||||
|
||||
// can not set tmfMPTCannotMutateCanConfidentialAmount without featureConfidentialTransfer
|
||||
auto const mutableFlags = ctx.tx[~sfMutableFlags];
|
||||
if (mutableFlags && (*mutableFlags & tmfMPTCannotMutateCanConfidentialAmount) &&
|
||||
!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#include <xrpl/protocol/ConfidentialTransfer.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
@@ -28,23 +27,16 @@ struct MPTMutabilityFlags
|
||||
{
|
||||
std::uint32_t setFlag;
|
||||
std::uint32_t clearFlag;
|
||||
std::uint32_t mutabilityFlag;
|
||||
std::uint32_t targetFlag;
|
||||
bool isCannotMutate = false; // if true, cannot mutate by default.
|
||||
std::uint32_t canMutateFlag;
|
||||
};
|
||||
|
||||
static constexpr std::array<MPTMutabilityFlags, 7> mptMutabilityFlags = {
|
||||
{{tmfMPTSetCanLock, tmfMPTClearCanLock, lsmfMPTCanMutateCanLock, lsfMPTCanLock},
|
||||
{tmfMPTSetRequireAuth, tmfMPTClearRequireAuth, lsmfMPTCanMutateRequireAuth, lsfMPTRequireAuth},
|
||||
{tmfMPTSetCanEscrow, tmfMPTClearCanEscrow, lsmfMPTCanMutateCanEscrow, lsfMPTCanEscrow},
|
||||
{tmfMPTSetCanTrade, tmfMPTClearCanTrade, lsmfMPTCanMutateCanTrade, lsfMPTCanTrade},
|
||||
{tmfMPTSetCanTransfer, tmfMPTClearCanTransfer, lsmfMPTCanMutateCanTransfer, lsfMPTCanTransfer},
|
||||
{tmfMPTSetCanClawback, tmfMPTClearCanClawback, lsmfMPTCanMutateCanClawback, lsfMPTCanClawback},
|
||||
{tmfMPTSetCanConfidentialAmount,
|
||||
tmfMPTClearCanConfidentialAmount,
|
||||
lsmfMPTCannotMutateCanConfidentialAmount,
|
||||
lsfMPTCanConfidentialAmount,
|
||||
true}}};
|
||||
static constexpr std::array<MPTMutabilityFlags, 6> mptMutabilityFlags = {
|
||||
{{tmfMPTSetCanLock, tmfMPTClearCanLock, lsmfMPTCanMutateCanLock},
|
||||
{tmfMPTSetRequireAuth, tmfMPTClearRequireAuth, lsmfMPTCanMutateRequireAuth},
|
||||
{tmfMPTSetCanEscrow, tmfMPTClearCanEscrow, lsmfMPTCanMutateCanEscrow},
|
||||
{tmfMPTSetCanTrade, tmfMPTClearCanTrade, lsmfMPTCanMutateCanTrade},
|
||||
{tmfMPTSetCanTransfer, tmfMPTClearCanTransfer, lsmfMPTCanMutateCanTransfer},
|
||||
{tmfMPTSetCanClawback, tmfMPTClearCanClawback, lsmfMPTCanMutateCanClawback}}};
|
||||
|
||||
NotTEC
|
||||
MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
@@ -53,28 +45,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
auto const metadata = ctx.tx[~sfMPTokenMetadata];
|
||||
auto const transferFee = ctx.tx[~sfTransferFee];
|
||||
auto const isMutate = mutableFlags || metadata || transferFee;
|
||||
auto const hasIssuerElGamalKey = ctx.tx.isFieldPresent(sfIssuerEncryptionKey);
|
||||
auto const hasAuditorElGamalKey = ctx.tx.isFieldPresent(sfAuditorEncryptionKey);
|
||||
auto const txFlags = ctx.tx.getFlags();
|
||||
|
||||
auto const mutatePrivacy = mutableFlags &&
|
||||
((*mutableFlags & (tmfMPTSetCanConfidentialAmount | tmfMPTClearCanConfidentialAmount)));
|
||||
|
||||
auto const hasDomain = ctx.tx.isFieldPresent(sfDomainID);
|
||||
auto const hasHolder = ctx.tx.isFieldPresent(sfHolder);
|
||||
|
||||
if (isMutate && !ctx.rules.enabled(featureDynamicMPT))
|
||||
return temDISABLED;
|
||||
|
||||
if ((hasIssuerElGamalKey || hasAuditorElGamalKey || mutatePrivacy) &&
|
||||
!ctx.rules.enabled(featureConfidentialTransfer))
|
||||
return temDISABLED;
|
||||
|
||||
if (hasDomain && hasHolder)
|
||||
if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder))
|
||||
return temMALFORMED;
|
||||
|
||||
if (mutatePrivacy && hasHolder)
|
||||
return temMALFORMED;
|
||||
auto const txFlags = ctx.tx.getFlags();
|
||||
|
||||
// fails if both flags are set
|
||||
if ((txFlags & tfMPTLock) && (txFlags & tfMPTUnlock))
|
||||
@@ -85,12 +63,10 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
if (holderID && accountID == holderID)
|
||||
return temMALFORMED;
|
||||
|
||||
if (ctx.rules.enabled(featureSingleAssetVault) || ctx.rules.enabled(featureDynamicMPT) ||
|
||||
ctx.rules.enabled(featureConfidentialTransfer))
|
||||
if (ctx.rules.enabled(featureSingleAssetVault) || ctx.rules.enabled(featureDynamicMPT))
|
||||
{
|
||||
// Is this transaction actually changing anything ?
|
||||
if (txFlags == 0 && !hasDomain && !hasIssuerElGamalKey && !hasAuditorElGamalKey &&
|
||||
!isMutate)
|
||||
if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID) && !isMutate)
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
@@ -131,18 +107,6 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasHolder && (hasIssuerElGamalKey || hasAuditorElGamalKey))
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasAuditorElGamalKey && !hasIssuerElGamalKey)
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasIssuerElGamalKey && !isValidCompressedECPoint(ctx.tx[sfIssuerEncryptionKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
if (hasAuditorElGamalKey && !isValidCompressedECPoint(ctx.tx[sfAuditorEncryptionKey]))
|
||||
return temMALFORMED;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -235,30 +199,16 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
return currentMutableFlags & mutableFlag;
|
||||
};
|
||||
|
||||
auto const mutableFlags = ctx.tx[~sfMutableFlags];
|
||||
if (mutableFlags)
|
||||
if (auto const mutableFlags = ctx.tx[~sfMutableFlags])
|
||||
{
|
||||
if (std::any_of(
|
||||
mptMutabilityFlags.begin(),
|
||||
mptMutabilityFlags.end(),
|
||||
[mutableFlags, &isMutableFlag](auto const& f) {
|
||||
bool const canMutate = f.isCannotMutate ? isMutableFlag(f.mutabilityFlag)
|
||||
: !isMutableFlag(f.mutabilityFlag);
|
||||
return canMutate && (*mutableFlags & (f.setFlag | f.clearFlag));
|
||||
return !isMutableFlag(f.canMutateFlag) &&
|
||||
((*mutableFlags & (f.setFlag | f.clearFlag)));
|
||||
}))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
if ((*mutableFlags & tmfMPTSetCanConfidentialAmount) ||
|
||||
(*mutableFlags & tmfMPTClearCanConfidentialAmount))
|
||||
{
|
||||
std::uint64_t const confidentialOA =
|
||||
(*sleMptIssuance)[~sfConfidentialOutstandingAmount].value_or(0);
|
||||
|
||||
// If there's any confidential outstanding amount, disallow toggling
|
||||
// the lsfMPTCanConfidentialAmount flag
|
||||
if (confidentialOA > 0)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isMutableFlag(lsmfMPTCanMutateMetadata) && ctx.tx.isFieldPresent(sfMPTokenMetadata))
|
||||
@@ -277,40 +227,6 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// cannot update issuer public key
|
||||
if (ctx.tx.isFieldPresent(sfIssuerEncryptionKey) &&
|
||||
sleMptIssuance->isFieldPresent(sfIssuerEncryptionKey))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// cannot update auditor public key
|
||||
if (ctx.tx.isFieldPresent(sfAuditorEncryptionKey) &&
|
||||
sleMptIssuance->isFieldPresent(sfAuditorEncryptionKey))
|
||||
{
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfIssuerEncryptionKey) &&
|
||||
!sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfAuditorEncryptionKey) &&
|
||||
!sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount))
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// cannot upload key if there's circulating supply of COA
|
||||
if ((ctx.tx.isFieldPresent(sfIssuerEncryptionKey) ||
|
||||
ctx.tx.isFieldPresent(sfAuditorEncryptionKey)) &&
|
||||
sleMptIssuance->isFieldPresent(sfConfidentialOutstandingAmount))
|
||||
{
|
||||
return tecNO_PERMISSION; // LCOV_EXCL_LINE
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -344,9 +260,9 @@ MPTokenIssuanceSet::doApply()
|
||||
for (auto const& f : mptMutabilityFlags)
|
||||
{
|
||||
if (mutableFlags & f.setFlag)
|
||||
flagsOut |= f.targetFlag;
|
||||
flagsOut |= f.canMutateFlag;
|
||||
else if (mutableFlags & f.clearFlag)
|
||||
flagsOut &= ~f.targetFlag;
|
||||
flagsOut &= ~f.canMutateFlag;
|
||||
}
|
||||
|
||||
if (mutableFlags & tmfMPTClearCanTransfer)
|
||||
@@ -398,26 +314,6 @@ MPTokenIssuanceSet::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const pubKey = ctx_.tx[~sfIssuerEncryptionKey])
|
||||
{
|
||||
// This is enforced in preflight.
|
||||
XRPL_ASSERT(
|
||||
sle->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
|
||||
|
||||
sle->setFieldVL(sfIssuerEncryptionKey, *pubKey);
|
||||
}
|
||||
|
||||
if (auto const pubKey = ctx_.tx[~sfAuditorEncryptionKey])
|
||||
{
|
||||
// This is enforced in preflight.
|
||||
XRPL_ASSERT(
|
||||
sle->getType() == ltMPTOKEN_ISSUANCE,
|
||||
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
|
||||
|
||||
sle->setFieldVL(sfAuditorEncryptionKey, *pubKey);
|
||||
}
|
||||
|
||||
view().update(sle);
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -2342,7 +2342,7 @@ private:
|
||||
// The vote is not added to the slots
|
||||
ammAlice.vote(carol, 1'000);
|
||||
auto const info = ammAlice.ammRpcInfo()[jss::amm][jss::vote_slots];
|
||||
for (std::uint16_t i = 0; i < info.size(); ++i)
|
||||
for (std::uint32_t i = 0; i < info.size(); ++i)
|
||||
BEAST_EXPECT(info[i][jss::account] != carol.human());
|
||||
// But the slots are refreshed and the fee is changed
|
||||
BEAST_EXPECT(ammAlice.expectTradingFee(82));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user