mirror of
https://github.com/XRPLF/rippled.git
synced 2026-03-07 21:32:31 +00:00
Compare commits
3 Commits
tapanito/t
...
a1q123456/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058b38a488 | ||
|
|
f638cbae3e | ||
|
|
94737f399a |
@@ -1,98 +0,0 @@
|
||||
# Custom CMake command definitions for gersemi formatting.
|
||||
# These stubs teach gersemi the signatures of project-specific commands
|
||||
# so it can format their invocations correctly.
|
||||
|
||||
function(git_branch branch_val)
|
||||
endfunction()
|
||||
|
||||
function(isolate_headers target A B scope)
|
||||
endfunction()
|
||||
|
||||
function(create_symbolic_link target link)
|
||||
endfunction()
|
||||
|
||||
function(xrpl_add_test name)
|
||||
endfunction()
|
||||
|
||||
macro(exclude_from_default target_)
|
||||
endmacro()
|
||||
|
||||
macro(exclude_if_included target_)
|
||||
endmacro()
|
||||
|
||||
function(target_protobuf_sources target prefix)
|
||||
set(options APPEND_PATH DESCRIPTORS)
|
||||
set(oneValueArgs
|
||||
LANGUAGE
|
||||
OUT_VAR
|
||||
EXPORT_MACRO
|
||||
TARGET
|
||||
PROTOC_OUT_DIR
|
||||
PLUGIN
|
||||
PLUGIN_OPTIONS
|
||||
PROTOC_EXE
|
||||
)
|
||||
set(multiValueArgs
|
||||
PROTOS
|
||||
IMPORT_DIRS
|
||||
GENERATE_EXTENSIONS
|
||||
PROTOC_OPTIONS
|
||||
DEPENDENCIES
|
||||
)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(add_module parent name)
|
||||
endfunction()
|
||||
|
||||
function(target_link_modules parent scope)
|
||||
endfunction()
|
||||
|
||||
function(setup_target_for_coverage_gcovr)
|
||||
set(options NONE)
|
||||
set(oneValueArgs BASE_DIRECTORY NAME FORMAT)
|
||||
set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
|
||||
function(add_code_coverage_to_target name scope)
|
||||
endfunction()
|
||||
|
||||
function(verbose_find_path variable name)
|
||||
set(options
|
||||
NO_CACHE
|
||||
REQUIRED
|
||||
OPTIONAL
|
||||
NO_DEFAULT_PATH
|
||||
NO_PACKAGE_ROOT_PATH
|
||||
NO_CMAKE_PATH
|
||||
NO_CMAKE_ENVIRONMENT_PATH
|
||||
NO_SYSTEM_ENVIRONMENT_PATH
|
||||
NO_CMAKE_SYSTEM_PATH
|
||||
NO_CMAKE_INSTALL_PREFIX
|
||||
CMAKE_FIND_ROOT_PATH_BOTH
|
||||
ONLY_CMAKE_FIND_ROOT_PATH
|
||||
NO_CMAKE_FIND_ROOT_PATH
|
||||
)
|
||||
set(oneValueArgs REGISTRY_VIEW VALIDATOR DOC)
|
||||
set(multiValueArgs NAMES HINTS PATHS PATH_SUFFIXES)
|
||||
cmake_parse_arguments(
|
||||
THIS_FUNCTION_PREFIX
|
||||
"${options}"
|
||||
"${oneValueArgs}"
|
||||
"${multiValueArgs}"
|
||||
${ARGN}
|
||||
)
|
||||
endfunction()
|
||||
@@ -1 +0,0 @@
|
||||
definitions: [.gersemi]
|
||||
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`
|
||||
|
||||
369
.github/scripts/levelization/generate.py
vendored
Normal file
369
.github/scripts/levelization/generate.py
vendored
Normal file
@@ -0,0 +1,369 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Usage: generate.py
|
||||
This script takes no parameters, and can be run from any directory,
|
||||
as long as it is in the expected.
|
||||
location in the repo.
|
||||
"""
|
||||
|
||||
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_repo_root(start_path: Path, depth_limit: int = 10) -> Path:
|
||||
"""
|
||||
Find the repository root by looking for .git directory or src/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
|
||||
# Check if this directory has src or include folders
|
||||
has_src = (current / "src").exists()
|
||||
has_include = (current / "include").exists()
|
||||
|
||||
if has_src or has_include:
|
||||
return current
|
||||
|
||||
# Check if this is a git repository root
|
||||
if (current / ".git").exists():
|
||||
# Check if it has src or include nearby
|
||||
if has_src or has_include:
|
||||
return current
|
||||
|
||||
# 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 get_scan_directories(repo_root: Path) -> List[Path]:
|
||||
"""
|
||||
Get the list of directories to scan for include files.
|
||||
Returns paths that actually exist.
|
||||
"""
|
||||
directories = []
|
||||
|
||||
for dir_name in ["include", "src"]:
|
||||
dir_path = repo_root / dir_name
|
||||
if dir_path.exists() and dir_path.is_dir():
|
||||
directories.append(dir_path)
|
||||
|
||||
if not directories:
|
||||
raise RuntimeError(f"No 'src' or 'include' directories found in {repo_root}")
|
||||
|
||||
return directories
|
||||
|
||||
|
||||
def main():
|
||||
# Change to the script's directory
|
||||
script_dir = Path(__file__).parent.resolve()
|
||||
os.chdir(script_dir)
|
||||
|
||||
# If the shell is interactive, clean up any flotsam before analyzing
|
||||
# Match bash behavior: check if PS1 is set (indicates interactive shell)
|
||||
# When running a script, PS1 is not set even if stdin/stdout are TTYs
|
||||
if os.environ.get("PS1"):
|
||||
try:
|
||||
subprocess.run(["git", "clean", "-ix"], check=False, timeout=30)
|
||||
except (subprocess.TimeoutExpired, KeyboardInterrupt):
|
||||
print("Skipping git clean...")
|
||||
except Exception:
|
||||
# If git clean fails for any reason, just continue
|
||||
pass
|
||||
|
||||
# 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 = find_repo_root(script_dir)
|
||||
scan_dirs = get_scan_directories(repo_root)
|
||||
|
||||
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 larger buffer for better 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:
|
||||
level = get_level(file_path)
|
||||
include_level = extract_include_level(include_line)
|
||||
|
||||
if include_level and 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 behavior (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 optimization - 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
|
||||
@@ -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: |
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -75,6 +75,9 @@ DerivedData
|
||||
/.claude
|
||||
/CLAUDE.md
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
|
||||
# Direnv's directory
|
||||
/.direnv
|
||||
|
||||
|
||||
@@ -11,12 +11,7 @@ function(xrpl_add_test name)
|
||||
)
|
||||
add_executable(${target} ${ARGN} ${sources})
|
||||
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
"${CMAKE_SOURCE_DIR}/tests/${name}"
|
||||
PRIVATE
|
||||
)
|
||||
isolate_headers(${target} "${CMAKE_SOURCE_DIR}" "${CMAKE_SOURCE_DIR}/tests/${name}" PRIVATE)
|
||||
|
||||
add_test(NAME ${target} COMMAND ${target})
|
||||
endfunction()
|
||||
|
||||
@@ -10,25 +10,16 @@ include(target_protobuf_sources)
|
||||
# so we just build them as a separate library.
|
||||
add_library(xrpl.libpb)
|
||||
set_target_properties(xrpl.libpb PROPERTIES UNITY_BUILD OFF)
|
||||
target_protobuf_sources(
|
||||
xrpl.libpb
|
||||
xrpl/proto
|
||||
LANGUAGE cpp
|
||||
IMPORT_DIRS include/xrpl/proto
|
||||
PROTOS include/xrpl/proto/xrpl.proto
|
||||
target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto
|
||||
PROTOS include/xrpl/proto/xrpl.proto
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE protos "include/xrpl/proto/org/*.proto")
|
||||
target_protobuf_sources(
|
||||
xrpl.libpb
|
||||
xrpl/proto
|
||||
LANGUAGE cpp
|
||||
IMPORT_DIRS include/xrpl/proto
|
||||
PROTOS "${protos}"
|
||||
target_protobuf_sources(xrpl.libpb xrpl/proto LANGUAGE cpp IMPORT_DIRS include/xrpl/proto
|
||||
PROTOS "${protos}"
|
||||
)
|
||||
target_protobuf_sources(
|
||||
xrpl.libpb
|
||||
xrpl/proto
|
||||
xrpl.libpb xrpl/proto
|
||||
LANGUAGE grpc
|
||||
IMPORT_DIRS include/xrpl/proto
|
||||
PROTOS "${protos}"
|
||||
|
||||
@@ -39,15 +39,19 @@ list(
|
||||
)
|
||||
|
||||
setup_target_for_coverage_gcovr(
|
||||
NAME coverage
|
||||
FORMAT ${coverage_format}
|
||||
NAME
|
||||
coverage
|
||||
FORMAT
|
||||
${coverage_format}
|
||||
EXCLUDE
|
||||
"src/test"
|
||||
"src/tests"
|
||||
"include/xrpl/beast/test"
|
||||
"include/xrpl/beast/unit_test"
|
||||
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
DEPENDENCIES xrpld xrpl.tests
|
||||
"src/test"
|
||||
"src/tests"
|
||||
"include/xrpl/beast/test"
|
||||
"include/xrpl/beast/unit_test"
|
||||
"${CMAKE_BINARY_DIR}/pb-xrpl.libpb"
|
||||
DEPENDENCIES
|
||||
xrpld
|
||||
xrpl.tests
|
||||
)
|
||||
|
||||
add_code_coverage_to_target(opts INTERFACE)
|
||||
|
||||
@@ -42,11 +42,7 @@ function(verbose_find_path variable name)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
verbose_find_path(
|
||||
doxygen_plantuml_jar_path
|
||||
plantuml.jar
|
||||
PATH_SUFFIXES share/plantuml
|
||||
)
|
||||
verbose_find_path(doxygen_plantuml_jar_path plantuml.jar PATH_SUFFIXES share/plantuml)
|
||||
verbose_find_path(doxygen_dot_path dot)
|
||||
|
||||
# https://en.cppreference.com/w/Cppreference:Archives
|
||||
|
||||
@@ -25,16 +25,10 @@ function(add_module parent name)
|
||||
${target}
|
||||
PUBLIC "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
|
||||
)
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}"
|
||||
PUBLIC
|
||||
isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/${parent}/${name}" PUBLIC
|
||||
)
|
||||
isolate_headers(
|
||||
${target}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}"
|
||||
PRIVATE
|
||||
isolate_headers(${target} "${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/lib${parent}/${name}" PRIVATE
|
||||
)
|
||||
endfunction()
|
||||
|
||||
@@ -42,7 +42,7 @@ TRANSACTION(ttPAYMENT, 0, Payment,
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/escrow/EscrowCreate.h>
|
||||
# include <xrpl/tx/transactors/escrow/Escrow.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
Delegation::delegable,
|
||||
@@ -58,9 +58,6 @@ 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{},
|
||||
@@ -97,7 +94,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet,
|
||||
|
||||
/** This transaction type cancels an existing escrow. */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/escrow/EscrowCancel.h>
|
||||
# include <xrpl/tx/transactors/escrow/Escrow.h>
|
||||
#endif
|
||||
TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel,
|
||||
Delegation::delegable,
|
||||
@@ -183,7 +180,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/PayChanCreate.h>
|
||||
# include <xrpl/tx/transactors/payment_channel/PayChan.h>
|
||||
#endif
|
||||
TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate,
|
||||
Delegation::delegable,
|
||||
@@ -199,9 +196,6 @@ 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{},
|
||||
@@ -213,9 +207,6 @@ 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{},
|
||||
@@ -626,7 +617,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge,
|
||||
|
||||
/** This transaction type creates or updates a DID */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/did/DIDSet.h>
|
||||
# include <xrpl/tx/transactors/did/DID.h>
|
||||
#endif
|
||||
TRANSACTION(ttDID_SET, 49, DIDSet,
|
||||
Delegation::delegable,
|
||||
@@ -639,9 +630,6 @@ 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,
|
||||
@@ -751,7 +739,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize,
|
||||
|
||||
/** This transaction type create an Credential instance */
|
||||
#if TRANSACTION_INCLUDE
|
||||
# include <xrpl/tx/transactors/credentials/CredentialCreate.h>
|
||||
# include <xrpl/tx/transactors/credentials/Credentials.h>
|
||||
#endif
|
||||
TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate,
|
||||
Delegation::delegable,
|
||||
@@ -765,9 +753,6 @@ 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,
|
||||
@@ -778,9 +763,6 @@ 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,
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#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
|
||||
@@ -1,29 +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;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,29 +0,0 @@
|
||||
#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
|
||||
77
include/xrpl/tx/transactors/credentials/Credentials.h
Normal file
77
include/xrpl/tx/transactors/credentials/Credentials.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#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,6 +4,24 @@
|
||||
|
||||
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:
|
||||
@@ -1,23 +0,0 @@
|
||||
#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
|
||||
80
include/xrpl/tx/transactors/escrow/Escrow.h
Normal file
80
include/xrpl/tx/transactors/escrow/Escrow.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#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
|
||||
@@ -1,26 +0,0 @@
|
||||
#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
|
||||
@@ -1,29 +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;
|
||||
};
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,35 +0,0 @@
|
||||
#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,229 +0,0 @@
|
||||
#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
|
||||
83
include/xrpl/tx/transactors/payment_channel/PayChan.h
Normal file
83
include/xrpl/tx/transactors/payment_channel/PayChan.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#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
|
||||
@@ -1,34 +0,0 @@
|
||||
#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
|
||||
@@ -1,31 +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;
|
||||
|
||||
} // namespace xrpl
|
||||
@@ -1,28 +0,0 @@
|
||||
#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
|
||||
@@ -1,15 +0,0 @@
|
||||
#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
|
||||
@@ -11,7 +11,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/DIDDelete.h>
|
||||
#include <xrpl/tx/transactors/did/DID.h>
|
||||
#include <xrpl/tx/transactors/nft/NFTokenUtils.h>
|
||||
#include <xrpl/tx/transactors/oracle/DeleteOracle.h>
|
||||
#include <xrpl/tx/transactors/payment/DepositPreauth.h>
|
||||
|
||||
@@ -1,113 +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/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
|
||||
@@ -1,164 +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/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
|
||||
@@ -1,90 +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/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
|
||||
341
src/libxrpl/tx/transactors/credentials/Credentials.cpp
Normal file
341
src/libxrpl/tx/transactors/credentials/Credentials.cpp
Normal file
@@ -0,0 +1,341 @@
|
||||
#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
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/did/DIDSet.h>
|
||||
#include <xrpl/tx/transactors/did/DID.h>
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
@@ -134,4 +134,54 @@ 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
|
||||
@@ -1,59 +0,0 @@
|
||||
#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
|
||||
1262
src/libxrpl/tx/transactors/escrow/Escrow.cpp
Normal file
1262
src/libxrpl/tx/transactors/escrow/Escrow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,200 +0,0 @@
|
||||
#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 <xrpl/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
|
||||
@@ -1,498 +0,0 @@
|
||||
#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
|
||||
@@ -1,371 +0,0 @@
|
||||
#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 <xrpl/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
|
||||
542
src/libxrpl/tx/transactors/payment_channel/PayChan.cpp
Normal file
542
src/libxrpl/tx/transactors/payment_channel/PayChan.cpp
Normal file
@@ -0,0 +1,542 @@
|
||||
#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
|
||||
@@ -1,185 +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/PayChan.h>
|
||||
#include <xrpl/protocol/PublicKey.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PayChanClaim.h>
|
||||
#include <xrpl/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
|
||||
@@ -1,173 +0,0 @@
|
||||
#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
|
||||
@@ -1,91 +0,0 @@
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/tx/transactors/payment_channel/PayChanFund.h>
|
||||
#include <xrpl/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
|
||||
@@ -1,57 +0,0 @@
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/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
|
||||
Reference in New Issue
Block a user