mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-22 03:27:08 +00:00
Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
607d0f7e02 | ||
|
|
8461c7d0b6 | ||
|
|
edb4f0342c | ||
|
|
ea17abb92a | ||
|
|
35a40a8e62 | ||
|
|
d494bf45b2 | ||
|
|
8bf4a5cbff | ||
|
|
58c2c82a30 | ||
|
|
11edaa441d | ||
|
|
a5e953b191 | ||
|
|
506ae12a8c | ||
|
|
0310c5cbe0 | ||
|
|
053e1af7ff | ||
|
|
7e24adbdd0 | ||
|
|
621df422a7 | ||
|
|
0a34b5c691 | ||
|
|
e0bc3dd51f | ||
|
|
dacecd24ba |
No files matched your search
+38
-38
@@ -1,5 +1,5 @@
|
||||
---
|
||||
Language: Cpp
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
@@ -19,52 +19,52 @@ AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [Q_FOREACH, BOOST_FOREACH]
|
||||
IncludeBlocks: Regroup
|
||||
ForEachMacros: [ Q_FOREACH, BOOST_FOREACH ]
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: "^<(test)/"
|
||||
Priority: 0
|
||||
- Regex: "^<(xrpld)/"
|
||||
Priority: 1
|
||||
- Regex: "^<(xrpl)/"
|
||||
Priority: 2
|
||||
- Regex: "^<(boost)/"
|
||||
Priority: 3
|
||||
- Regex: "^.*/"
|
||||
Priority: 4
|
||||
- Regex: '^.*\.h'
|
||||
Priority: 5
|
||||
- Regex: ".*"
|
||||
Priority: 6
|
||||
IncludeIsMainRegex: "$"
|
||||
- Regex: '^<(test)/'
|
||||
Priority: 0
|
||||
- Regex: '^<(xrpld)/'
|
||||
Priority: 1
|
||||
- Regex: '^<(xrpl)/'
|
||||
Priority: 2
|
||||
- Regex: '^<(boost)/'
|
||||
Priority: 3
|
||||
- Regex: '^.*/'
|
||||
Priority: 4
|
||||
- Regex: '^.*\.h'
|
||||
Priority: 5
|
||||
- Regex: '.*'
|
||||
Priority: 6
|
||||
IncludeIsMainRegex: '$'
|
||||
IndentCaseLabels: true
|
||||
IndentFunctionDeclarationAfterType: false
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
@@ -78,20 +78,20 @@ PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
ReflowComments: true
|
||||
RequiresClausePosition: OwnLine
|
||||
SortIncludes: true
|
||||
SortIncludes: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
QualifierAlignment: Right
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
QualifierAlignment: Right
|
||||
@@ -17,7 +17,6 @@ runs:
|
||||
conan export external/rocksdb rocksdb/9.7.3@
|
||||
conan export external/soci soci/4.0.3@
|
||||
conan export external/nudb nudb/2.0.8@
|
||||
conan export -k external/wamr wamr/2.2.0@
|
||||
- name: add Ripple Conan remote
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -408,7 +408,6 @@ jobs:
|
||||
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default
|
||||
conan export external/snappy snappy/1.1.10@
|
||||
conan export external/soci soci/4.0.3@
|
||||
conan export -k external/wamr wamr/2.2.0@
|
||||
|
||||
- name: build dependencies
|
||||
run: |
|
||||
|
||||
@@ -204,17 +204,6 @@ It fixes some source files to add missing `#include`s.
|
||||
conan export --version 2.0.8 external/nudb
|
||||
```
|
||||
|
||||
Export our [Conan recipe for WAMR](./external/wamr).
|
||||
It add metering and expose some internal structures.
|
||||
|
||||
|
||||
```
|
||||
# Conan 1.x
|
||||
conan export external/wamr wamr/2.2.0@
|
||||
# Conan 2.x
|
||||
conan export --version 2.2.0 external/wamr
|
||||
```
|
||||
|
||||
### Build and Test
|
||||
|
||||
1. Create a build directory and move into it.
|
||||
|
||||
@@ -115,7 +115,6 @@ endif()
|
||||
find_package(nudb REQUIRED)
|
||||
find_package(date REQUIRED)
|
||||
find_package(xxHash REQUIRED)
|
||||
find_package(wamr REQUIRED)
|
||||
|
||||
target_link_libraries(ripple_libs INTERFACE
|
||||
ed25519::ed25519
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ To report a qualifying bug, please send a detailed report to:
|
||||
|Long Key ID | `0xCD49A0AFC57929BE` |
|
||||
|Fingerprint | `24E6 3B02 37E0 FA9C 5E96 8974 CD49 A0AF C579 29BE` |
|
||||
|
||||
The full PGP key for this address, which is also available on several key servers (e.g. on [keys.gnupg.net](https://keys.gnupg.net)), is:
|
||||
The full PGP key for this address, which is also available on several key servers (e.g. on [keyserver.ubuntu.com](https://keyserver.ubuntu.com)), is:
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
mQINBFUwGHYBEAC0wpGpBPkd8W1UdQjg9+cEFzeIEJRaoZoeuJD8mofwI5Ejnjdt
|
||||
|
||||
@@ -1249,39 +1249,6 @@
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
#
|
||||
# extension_compute_limit = <gas>
|
||||
#
|
||||
# The extension compute limit is the maximum amount of gas that can be
|
||||
# consumed by a single transaction. The gas limit is used to prevent
|
||||
# transactions from consuming too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, rippled will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# extension_compute_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# extension_size_limit = <bytes>
|
||||
#
|
||||
# The extension size limit is the maximum size of a WASM extension in
|
||||
# bytes. The size limit is used to prevent extensions from consuming
|
||||
# too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, rippled will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# extension_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <bytes>
|
||||
#
|
||||
# The gas price is the conversion between WASM gas and its price in drops.
|
||||
#
|
||||
# If this parameter is unspecified, rippled will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_price = 1000000 # 1 drop per gas
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 9. Misc Settings
|
||||
|
||||
@@ -65,7 +65,6 @@ target_link_libraries(xrpl.imports.main
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
wamr::wamr
|
||||
)
|
||||
|
||||
include(add_module)
|
||||
|
||||
@@ -2,16 +2,6 @@
|
||||
convenience variables and sanity checks
|
||||
#]===================================================================]
|
||||
|
||||
include(ProcessorCount)
|
||||
|
||||
if (NOT ep_procs)
|
||||
ProcessorCount(ep_procs)
|
||||
if (ep_procs GREATER 1)
|
||||
# never use more than half of cores for EP builds
|
||||
math (EXPR ep_procs "${ep_procs} / 2")
|
||||
message (STATUS "Using ${ep_procs} cores for ExternalProject builds.")
|
||||
endif ()
|
||||
endif ()
|
||||
get_property(is_multiconfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
|
||||
set (CMAKE_CONFIGURATION_TYPES "Debug;Release" CACHE STRING "" FORCE)
|
||||
|
||||
+6
-7
@@ -1,8 +1,7 @@
|
||||
from conan import ConanFile
|
||||
from conan import ConanFile, __version__ as conan_version
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
import re
|
||||
|
||||
|
||||
class Xrpl(ConanFile):
|
||||
name = 'xrpl'
|
||||
|
||||
@@ -25,15 +24,12 @@ class Xrpl(ConanFile):
|
||||
}
|
||||
|
||||
requires = [
|
||||
'date/3.0.3',
|
||||
'grpc/1.50.1',
|
||||
'libarchive/3.7.6',
|
||||
'nudb/2.0.8',
|
||||
'openssl/1.1.1v',
|
||||
'soci/4.0.3',
|
||||
'xxhash/0.8.2',
|
||||
'zlib/1.3.1',
|
||||
'wamr/2.2.0',
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
@@ -101,7 +97,10 @@ class Xrpl(ConanFile):
|
||||
self.options['boost'].visibility = 'global'
|
||||
|
||||
def requirements(self):
|
||||
self.requires('boost/1.83.0', force=True)
|
||||
# Conan 2 requires transitive headers to be specified
|
||||
transitive_headers_opt = {'transitive_headers': True} if conan_version.split('.')[0] == '2' else {}
|
||||
self.requires('boost/1.83.0', force=True, **transitive_headers_opt)
|
||||
self.requires('date/3.0.3', **transitive_headers_opt)
|
||||
self.requires('lz4/1.10.0', force=True)
|
||||
self.requires('protobuf/3.21.9', force=True)
|
||||
self.requires('sqlite3/3.47.0', force=True)
|
||||
@@ -109,6 +108,7 @@ class Xrpl(ConanFile):
|
||||
self.requires('jemalloc/5.3.0')
|
||||
if self.options.rocksdb:
|
||||
self.requires('rocksdb/9.7.3')
|
||||
self.requires('xxhash/0.8.2', **transitive_headers_opt)
|
||||
|
||||
exports_sources = (
|
||||
'CMakeLists.txt',
|
||||
@@ -127,7 +127,6 @@ class Xrpl(ConanFile):
|
||||
self.folders.generators = 'build/generators'
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables['tests'] = self.options.tests
|
||||
|
||||
Vendored
-6
@@ -1,6 +0,0 @@
|
||||
patches:
|
||||
2.2.0:
|
||||
- patch_description: add metering to iwasm interpreter
|
||||
patch_file: patches/ripp_metering.patch
|
||||
patch_type: conan
|
||||
|
||||
Vendored
-91
@@ -1,91 +0,0 @@
|
||||
from conans import ConanFile, tools
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps, cmake_layout
|
||||
from conan.tools.files import (
|
||||
apply_conandata_patches,
|
||||
export_conandata_patches,
|
||||
# get,
|
||||
)
|
||||
|
||||
# import os
|
||||
|
||||
required_conan_version = ">=1.55.0"
|
||||
|
||||
|
||||
class WamrConan(ConanFile):
|
||||
name = "wamr"
|
||||
version = "2.2.0"
|
||||
license = "Apache License v2.0"
|
||||
url = "https://github.com/bytecodealliance/wasm-micro-runtime.git"
|
||||
description = "Webassembly micro runtime"
|
||||
package_type = "library"
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
options = {"shared": [True, False], "fPIC": [True, False]}
|
||||
default_options = {"shared": False, "fPIC": True}
|
||||
generators = "CMakeToolchain", "CMakeDeps"
|
||||
# requires = [("llvm/20.1.1@")]
|
||||
|
||||
def export_sources(self):
|
||||
export_conandata_patches(self)
|
||||
pass
|
||||
|
||||
# def build_requirements(self):
|
||||
# self.tool_requires("llvm/20.1.1")
|
||||
|
||||
def config_options(self):
|
||||
if self.settings.os == "Windows":
|
||||
del self.options.fPIC
|
||||
|
||||
def layout(self):
|
||||
cmake_layout(self, src_folder="src")
|
||||
|
||||
def source(self):
|
||||
git = tools.Git()
|
||||
git.clone(
|
||||
"https://github.com/bytecodealliance/wasm-micro-runtime.git",
|
||||
"c883fafead005e87ad3122b05409886f507c1cb0",
|
||||
shallow=True,
|
||||
)
|
||||
# get(self, **self.conan_data["sources"][self.version], strip_root=True)
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
|
||||
tc.variables["WAMR_BUILD_INTERP"] = 1
|
||||
tc.variables["WAMR_BUILD_FAST_INTERP"] = 1
|
||||
tc.variables["WAMR_BUILD_INSTRUCTION_METERING"] = 1
|
||||
tc.variables["WAMR_BUILD_AOT"] = 0
|
||||
tc.variables["WAMR_BUILD_JIT"] = 0
|
||||
tc.variables["WAMR_BUILD_FAST_JIT"] = 0
|
||||
tc.variables["WAMR_DISABLE_HW_BOUND_CHECK"] = 1
|
||||
tc.variables["WAMR_DISABLE_STACK_HW_BOUND_CHECK"] = 1
|
||||
tc.variables["WAMR_BH_LOG"] = "wamr_log_to_rippled"
|
||||
# tc.variables["WAMR_BUILD_FAST_JIT"] = 0 if self.settings.os == "Windows" else 1
|
||||
# ll_dep = self.dependencies["llvm"]
|
||||
# self.output.info(f"-----------package_folder: {type(ll_dep.__dict__)}")
|
||||
# tc.variables["LLVM_DIR"] = os.path.join(ll_dep.package_folder, "lib", "cmake", "llvm")
|
||||
tc.generate()
|
||||
|
||||
# This generates "foo-config.cmake" and "bar-config.cmake" in self.generators_folder
|
||||
deps = CMakeDeps(self)
|
||||
deps.generate()
|
||||
|
||||
def build(self):
|
||||
apply_conandata_patches(self)
|
||||
cmake = CMake(self)
|
||||
cmake.verbose = True
|
||||
cmake.configure()
|
||||
cmake.build()
|
||||
# self.run(f'echo {self.source_folder}')
|
||||
# Explicit way:
|
||||
# self.run('cmake %s/hello %s' % (self.source_folder, cmake.command_line))
|
||||
# self.run("cmake --build . %s" % cmake.build_config)
|
||||
|
||||
def package(self):
|
||||
cmake = CMake(self)
|
||||
cmake.verbose = True
|
||||
cmake.install()
|
||||
|
||||
def package_info(self):
|
||||
self.cpp_info.libs = ["iwasm"]
|
||||
self.cpp_info.names["cmake_find_package"] = "wamr"
|
||||
self.cpp_info.names["cmake_find_package_multi"] = "wamr"
|
||||
-470
@@ -1,470 +0,0 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 551991f8..5f48a0b8 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2019 Intel Corporation. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
|
||||
-cmake_minimum_required (VERSION 3.14)
|
||||
+cmake_minimum_required (VERSION 3.20)
|
||||
|
||||
option(BUILD_SHARED_LIBS "Build using shared libraries" OFF)
|
||||
|
||||
diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake
|
||||
index 1cb50235..bd103022 100644
|
||||
--- a/build-scripts/config_common.cmake
|
||||
+++ b/build-scripts/config_common.cmake
|
||||
@@ -669,6 +669,10 @@ if (WAMR_BUILD_AOT_VALIDATOR EQUAL 1)
|
||||
message (" AOT validator enabled")
|
||||
add_definitions (-DWASM_ENABLE_AOT_VALIDATOR=1)
|
||||
endif ()
|
||||
+if (WAMR_BUILD_INSTRUCTION_METERING EQUAL 1)
|
||||
+ message (" Instruction metering enabled")
|
||||
+ add_definitions (-DWASM_ENABLE_INSTRUCTION_METERING=1)
|
||||
+endif ()
|
||||
|
||||
########################################
|
||||
# Show Phase4 Wasm proposals status.
|
||||
diff --git a/core/config.h b/core/config.h
|
||||
index cb1189c9..a4e1499e 100644
|
||||
--- a/core/config.h
|
||||
+++ b/core/config.h
|
||||
@@ -716,4 +716,8 @@ unless used elsewhere */
|
||||
#define WASM_ENABLE_AOT_VALIDATOR 0
|
||||
#endif
|
||||
|
||||
+#ifndef WASM_ENABLE_INSTRUCTION_METERING
|
||||
+#define WASM_ENABLE_INSTRUCTION_METERING 0
|
||||
+#endif
|
||||
+
|
||||
#endif /* end of _CONFIG_H_ */
|
||||
diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c
|
||||
index 269ec577..bc6fd01b 100644
|
||||
--- a/core/iwasm/common/wasm_c_api.c
|
||||
+++ b/core/iwasm/common/wasm_c_api.c
|
||||
@@ -5389,3 +5389,8 @@ wasm_instance_get_wasm_func_exec_time(const wasm_instance_t *instance,
|
||||
return -1.0;
|
||||
#endif
|
||||
}
|
||||
+
|
||||
+wasm_exec_env_t wasm_instance_exec_env(const wasm_instance_t*instance)
|
||||
+{
|
||||
+ return wasm_runtime_get_exec_env_singleton(instance->inst_comm_rt);
|
||||
+}
|
||||
diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c
|
||||
index e33fd9f3..d1ff9c41 100644
|
||||
--- a/core/iwasm/common/wasm_exec_env.c
|
||||
+++ b/core/iwasm/common/wasm_exec_env.c
|
||||
@@ -85,6 +85,12 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
|
||||
wasm_runtime_dump_exec_env_mem_consumption(exec_env);
|
||||
#endif
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ exec_env->instructions_to_execute = -1;
|
||||
+ for(int i = 0; i < 256; ++i)
|
||||
+ exec_env->instructions_schedule[i] = 1;
|
||||
+#endif
|
||||
+
|
||||
return exec_env;
|
||||
|
||||
#ifdef OS_ENABLE_HW_BOUND_CHECK
|
||||
diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h
|
||||
index ce0c1fa7..2713a092 100644
|
||||
--- a/core/iwasm/common/wasm_exec_env.h
|
||||
+++ b/core/iwasm/common/wasm_exec_env.h
|
||||
@@ -87,6 +87,12 @@ typedef struct WASMExecEnv {
|
||||
uint8 *bottom;
|
||||
} wasm_stack;
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ /* instructions to execute */
|
||||
+ int64 instructions_to_execute;
|
||||
+ int64 instructions_schedule[256];
|
||||
+#endif
|
||||
+
|
||||
#if WASM_ENABLE_FAST_JIT != 0
|
||||
/**
|
||||
* Cache for
|
||||
diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c
|
||||
index d33c0272..21bfdf29 100644
|
||||
--- a/core/iwasm/common/wasm_runtime_common.c
|
||||
+++ b/core/iwasm/common/wasm_runtime_common.c
|
||||
@@ -2285,6 +2285,31 @@ wasm_runtime_access_exce_check_guard_page()
|
||||
}
|
||||
#endif
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+
|
||||
+void
|
||||
+wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
|
||||
+ int64 instructions_to_execute)
|
||||
+{
|
||||
+ exec_env->instructions_to_execute = instructions_to_execute;
|
||||
+}
|
||||
+
|
||||
+int64
|
||||
+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env)
|
||||
+{
|
||||
+ return exec_env->instructions_to_execute;
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+wasm_runtime_set_instruction_schedule(WASMExecEnv *exec_env,
|
||||
+ int64 const *instructions_schedule)
|
||||
+{
|
||||
+ for(int i = 0; i < 256; ++i)
|
||||
+ exec_env->instructions_schedule[i] = instructions_schedule[i];
|
||||
+}
|
||||
+
|
||||
+#endif
|
||||
+
|
||||
WASMFuncType *
|
||||
wasm_runtime_get_function_type(const WASMFunctionInstanceCommon *function,
|
||||
uint32 module_type)
|
||||
@@ -7792,13 +7817,14 @@ wasm_runtime_get_module_name(wasm_module_t module)
|
||||
bool
|
||||
wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
|
||||
{
|
||||
+#if WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
|
||||
uint8 *boundary = exec_env->native_stack_boundary;
|
||||
RECORD_STACK_USAGE(exec_env, (uint8 *)&boundary);
|
||||
if (boundary == NULL) {
|
||||
/* the platform doesn't support os_thread_get_stack_boundary */
|
||||
return true;
|
||||
}
|
||||
-#if defined(OS_ENABLE_HW_BOUND_CHECK) && WASM_DISABLE_STACK_HW_BOUND_CHECK == 0
|
||||
+#if defined(OS_ENABLE_HW_BOUND_CHECK)
|
||||
uint32 page_size = os_getpagesize();
|
||||
uint32 guard_page_count = STACK_OVERFLOW_CHECK_GUARD_PAGE_COUNT;
|
||||
boundary = boundary + page_size * guard_page_count;
|
||||
@@ -7808,6 +7834,7 @@ wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
|
||||
"native stack overflow");
|
||||
return false;
|
||||
}
|
||||
+#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7830,7 +7857,7 @@ wasm_runtime_detect_native_stack_overflow_size(WASMExecEnv *exec_env,
|
||||
boundary = boundary - WASM_STACK_GUARD_SIZE + requested_size;
|
||||
if ((uint8 *)&boundary < boundary) {
|
||||
wasm_runtime_set_exception(wasm_runtime_get_module_inst(exec_env),
|
||||
- "native stack overflow");
|
||||
+ "native s stack overflow");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h
|
||||
index 8ac032bf..5ca5d489 100644
|
||||
--- a/core/iwasm/common/wasm_runtime_common.h
|
||||
+++ b/core/iwasm/common/wasm_runtime_common.h
|
||||
@@ -791,9 +791,25 @@ WASM_RUNTIME_API_EXTERN void
|
||||
wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env,
|
||||
uint8 *native_stack_boundary);
|
||||
|
||||
-#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+
|
||||
/* See wasm_export.h for description */
|
||||
WASM_RUNTIME_API_EXTERN void
|
||||
+wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
|
||||
+ int64 instructions_to_execute);
|
||||
+WASM_RUNTIME_API_EXTERN int64
|
||||
+wasm_runtime_get_instruction_count_limit(WASMExecEnv *exec_env);
|
||||
+
|
||||
+WASM_RUNTIME_API_EXTERN void
|
||||
+wasm_runtime_set_instruction_schedule(WASMExecEnv *exec_env,
|
||||
+ int64 const *instructions_schedule);
|
||||
+
|
||||
+#endif
|
||||
+
|
||||
+#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
|
||||
+/* See wasm_export.h for description */
|
||||
+WASM_RUNTIME_API_EXTERN
|
||||
+void
|
||||
wasm_runtime_set_bounds_checks(WASMModuleInstanceCommon *module_inst,
|
||||
bool enable);
|
||||
|
||||
diff --git a/core/iwasm/include/wasm_c_api.h b/core/iwasm/include/wasm_c_api.h
|
||||
index 241a0eec..9eb0dde1 100644
|
||||
--- a/core/iwasm/include/wasm_c_api.h
|
||||
+++ b/core/iwasm/include/wasm_c_api.h
|
||||
@@ -19,8 +19,10 @@
|
||||
#if defined(_MSC_BUILD)
|
||||
#if defined(COMPILING_WASM_RUNTIME_API)
|
||||
#define WASM_API_EXTERN __declspec(dllexport)
|
||||
-#else
|
||||
+#elif defined(_DLL)
|
||||
#define WASM_API_EXTERN __declspec(dllimport)
|
||||
+#else
|
||||
+#define WASM_API_EXTERN
|
||||
#endif
|
||||
#else
|
||||
#define WASM_API_EXTERN
|
||||
@@ -701,6 +703,11 @@ WASM_API_EXTERN double wasm_instance_sum_wasm_exec_time(const wasm_instance_t*);
|
||||
// func_name. If the function is not found, return 0.
|
||||
WASM_API_EXTERN double wasm_instance_get_wasm_func_exec_time(const wasm_instance_t*, const char *);
|
||||
|
||||
+struct WASMExecEnv;
|
||||
+typedef struct WASMExecEnv *wasm_exec_env_t;
|
||||
+
|
||||
+WASM_API_EXTERN wasm_exec_env_t wasm_instance_exec_env(const wasm_instance_t*);
|
||||
+
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Convenience
|
||||
|
||||
diff --git a/core/iwasm/include/wasm_export.h b/core/iwasm/include/wasm_export.h
|
||||
index b73a0364..3fd0949f 100644
|
||||
--- a/core/iwasm/include/wasm_export.h
|
||||
+++ b/core/iwasm/include/wasm_export.h
|
||||
@@ -20,8 +20,10 @@
|
||||
#if defined(_MSC_BUILD)
|
||||
#if defined(COMPILING_WASM_RUNTIME_API)
|
||||
#define WASM_RUNTIME_API_EXTERN __declspec(dllexport)
|
||||
-#else
|
||||
+#elif defined(_DLL)
|
||||
#define WASM_RUNTIME_API_EXTERN __declspec(dllimport)
|
||||
+#else
|
||||
+#define WASM_RUNTIME_API_EXTERN
|
||||
#endif
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define WASM_RUNTIME_API_EXTERN __attribute__((visibility("default")))
|
||||
@@ -1821,6 +1823,27 @@ WASM_RUNTIME_API_EXTERN void
|
||||
wasm_runtime_set_native_stack_boundary(wasm_exec_env_t exec_env,
|
||||
uint8_t *native_stack_boundary);
|
||||
|
||||
+/**
|
||||
+ * Set the instruction count limit to the execution environment.
|
||||
+ * By default the instruction count limit is -1, which means no limit.
|
||||
+ * However, if the instruction count limit is set to a positive value,
|
||||
+ * the execution will be terminated when the instruction count reaches
|
||||
+ * the limit.
|
||||
+ *
|
||||
+ * @param exec_env the execution environment
|
||||
+ * @param instruction_count the instruction count limit
|
||||
+ */
|
||||
+WASM_RUNTIME_API_EXTERN void
|
||||
+wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env,
|
||||
+ int64_t instruction_count);
|
||||
+
|
||||
+WASM_RUNTIME_API_EXTERN int64_t
|
||||
+wasm_runtime_get_instruction_count_limit(wasm_exec_env_t exec_env);
|
||||
+
|
||||
+WASM_RUNTIME_API_EXTERN void
|
||||
+wasm_runtime_set_instruction_schedule(wasm_exec_env_t exec_env,
|
||||
+ int64_t const *instructions_schedule);
|
||||
+
|
||||
/**
|
||||
* Dump runtime memory consumption, including:
|
||||
* Exec env memory consumption
|
||||
diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c
|
||||
index 41ac4c72..bd8f714a 100644
|
||||
--- a/core/iwasm/interpreter/wasm_interp_classic.c
|
||||
+++ b/core/iwasm/interpreter/wasm_interp_classic.c
|
||||
@@ -1516,10 +1516,13 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
|
||||
} \
|
||||
os_mutex_unlock(&exec_env->wait_lock); \
|
||||
} \
|
||||
+ CHECK_INSTRUCTION_LIMIT(); \
|
||||
goto *handle_table[*frame_ip++]; \
|
||||
} while (0)
|
||||
#else
|
||||
-#define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH()
|
||||
+#define HANDLE_OP_END() \
|
||||
+ CHECK_INSTRUCTION_LIMIT(); \
|
||||
+ FETCH_OPCODE_AND_DISPATCH()
|
||||
#endif
|
||||
|
||||
#else /* else of WASM_ENABLE_LABELS_AS_VALUES */
|
||||
@@ -1542,9 +1545,12 @@ wasm_interp_call_func_import(WASMModuleInstance *module_inst,
|
||||
} \
|
||||
os_mutex_unlock(&exec_env->wait_lock); \
|
||||
} \
|
||||
+ CHECK_INSTRUCTION_LIMIT(); \
|
||||
continue;
|
||||
#else
|
||||
-#define HANDLE_OP_END() continue
|
||||
+#define HANDLE_OP_END() \
|
||||
+ CHECK_INSTRUCTION_LIMIT(); \
|
||||
+ continue;
|
||||
#endif
|
||||
|
||||
#endif /* end of WASM_ENABLE_LABELS_AS_VALUES */
|
||||
@@ -1562,6 +1568,20 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
|
||||
#endif
|
||||
}
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+#define CHECK_INSTRUCTION_LIMIT() \
|
||||
+ if (instructions_to_execute >= 0) \
|
||||
+ { \
|
||||
+ instructions_to_execute -= instructions_schedule[opcode];\
|
||||
+ if (instructions_to_execute < 0) { \
|
||||
+ wasm_set_exception(module, "instruction limit exceeded"); \
|
||||
+ goto got_exception; \
|
||||
+ } \
|
||||
+ }
|
||||
+#else
|
||||
+#define CHECK_INSTRUCTION_LIMIT() (void)0
|
||||
+#endif
|
||||
+
|
||||
static void
|
||||
wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
WASMExecEnv *exec_env,
|
||||
@@ -1605,6 +1625,17 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
uint32 local_idx, local_offset, global_idx;
|
||||
uint8 local_type, *global_addr;
|
||||
uint32 cache_index, type_index, param_cell_num, cell_num;
|
||||
+
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ int64 instructions_to_execute = -1;
|
||||
+ int64 const *instructions_schedule = NULL;
|
||||
+ if(exec_env)
|
||||
+ {
|
||||
+ instructions_to_execute = exec_env->instructions_to_execute;
|
||||
+ instructions_schedule = exec_env->instructions_schedule;
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
#if WASM_ENABLE_EXCE_HANDLING != 0
|
||||
int32_t exception_tag_index;
|
||||
#endif
|
||||
@@ -6859,6 +6890,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
FREE_FRAME(exec_env, frame);
|
||||
wasm_exec_env_set_cur_frame(exec_env, prev_frame);
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ if(exec_env)
|
||||
+ exec_env->instructions_to_execute = instructions_to_execute;
|
||||
+#endif
|
||||
+
|
||||
if (!prev_frame->ip) {
|
||||
/* Called from native. */
|
||||
return;
|
||||
@@ -6899,6 +6935,12 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
}
|
||||
#endif
|
||||
SYNC_ALL_TO_FRAME();
|
||||
+
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ if(exec_env)
|
||||
+ exec_env->instructions_to_execute = instructions_to_execute;
|
||||
+#endif
|
||||
+
|
||||
return;
|
||||
|
||||
#if WASM_ENABLE_LABELS_AS_VALUES == 0
|
||||
diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c
|
||||
index f33ad60e..9cbf2010 100644
|
||||
--- a/core/iwasm/interpreter/wasm_interp_fast.c
|
||||
+++ b/core/iwasm/interpreter/wasm_interp_fast.c
|
||||
@@ -105,6 +105,19 @@ typedef float64 CellType_F64;
|
||||
goto unaligned_atomic; \
|
||||
} while (0)
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+#define CHECK_INSTRUCTION_LIMIT() \
|
||||
+ if (instructions_to_execute >= 0) { \
|
||||
+ instructions_to_execute -= instructions_schedule[opcode]; \
|
||||
+ if (instructions_to_execute < 0) { \
|
||||
+ wasm_set_exception(module, "instruction limit exceeded"); \
|
||||
+ goto got_exception; \
|
||||
+ } \
|
||||
+ }
|
||||
+#else
|
||||
+#define CHECK_INSTRUCTION_LIMIT() (void)0
|
||||
+#endif
|
||||
+
|
||||
static inline uint32
|
||||
rotl32(uint32 n, uint32 c)
|
||||
{
|
||||
@@ -1466,12 +1479,14 @@ wasm_interp_dump_op_count()
|
||||
} while (0)
|
||||
#endif
|
||||
#endif /* end of WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS */
|
||||
-#define HANDLE_OP_END() FETCH_OPCODE_AND_DISPATCH()
|
||||
+#define HANDLE_OP_END() CHECK_INSTRUCTION_LIMIT(); FETCH_OPCODE_AND_DISPATCH()
|
||||
|
||||
#else /* else of WASM_ENABLE_LABELS_AS_VALUES */
|
||||
|
||||
#define HANDLE_OP(opcode) case opcode:
|
||||
-#define HANDLE_OP_END() continue
|
||||
+#define HANDLE_OP_END() \
|
||||
+ CHECK_INSTRUCTION_LIMIT(); \
|
||||
+ continue
|
||||
|
||||
#endif /* end of WASM_ENABLE_LABELS_AS_VALUES */
|
||||
|
||||
@@ -1538,6 +1553,16 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
uint8 *maddr = NULL;
|
||||
uint32 local_idx, local_offset, global_idx;
|
||||
uint8 opcode = 0, local_type, *global_addr;
|
||||
+
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ int64 instructions_to_execute = -1;
|
||||
+ int64 const *instructions_schedule = NULL;
|
||||
+ if (exec_env) {
|
||||
+ instructions_to_execute = exec_env->instructions_to_execute;
|
||||
+ instructions_schedule = exec_env->instructions_schedule;
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
#if !defined(OS_ENABLE_HW_BOUND_CHECK) \
|
||||
|| WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS == 0
|
||||
#if WASM_CONFIGURABLE_BOUNDS_CHECKS != 0
|
||||
@@ -7761,6 +7786,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
FREE_FRAME(exec_env, frame);
|
||||
wasm_exec_env_set_cur_frame(exec_env, (WASMRuntimeFrame *)prev_frame);
|
||||
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ if (exec_env)
|
||||
+ exec_env->instructions_to_execute = instructions_to_execute;
|
||||
+#endif
|
||||
+
|
||||
if (!prev_frame->ip)
|
||||
/* Called from native. */
|
||||
return;
|
||||
@@ -7789,6 +7819,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
|
||||
got_exception:
|
||||
SYNC_ALL_TO_FRAME();
|
||||
+#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
+ if (exec_env)
|
||||
+ exec_env->instructions_to_execute = instructions_to_execute;
|
||||
+#endif
|
||||
return;
|
||||
|
||||
#if WASM_ENABLE_LABELS_AS_VALUES == 0
|
||||
diff --git a/core/shared/platform/include/platform_wasi_types.h b/core/shared/platform/include/platform_wasi_types.h
|
||||
index ac1a95ea..e23b500e 100644
|
||||
--- a/core/shared/platform/include/platform_wasi_types.h
|
||||
+++ b/core/shared/platform/include/platform_wasi_types.h
|
||||
@@ -36,7 +36,11 @@ extern "C" {
|
||||
#if WASM_ENABLE_UVWASI != 0 || WASM_ENABLE_LIBC_WASI == 0
|
||||
#define assert_wasi_layout(expr, message) /* nothing */
|
||||
#else
|
||||
-#define assert_wasi_layout(expr, message) _Static_assert(expr, message)
|
||||
+ #ifndef _MSC_VER
|
||||
+ #define assert_wasi_layout(expr, message) _Static_assert(expr, message)
|
||||
+ #else
|
||||
+ #define assert_wasi_layout(expr, message) static_assert(expr, message)
|
||||
+ #endif
|
||||
#endif
|
||||
|
||||
assert_wasi_layout(_Alignof(int8_t) == 1, "non-wasi data layout");
|
||||
diff --git a/doc/build_wamr.md b/doc/build_wamr.md
|
||||
index 6425450b..94dd9628 100644
|
||||
--- a/doc/build_wamr.md
|
||||
+++ b/doc/build_wamr.md
|
||||
@@ -327,6 +327,10 @@ And the wasm app can calls below APIs to allocate/free memory from/to the shared
|
||||
- **WAMR_BUILD_SHRUNK_MEMORY**=1/0, default to enable if not set
|
||||
> Note: When enabled, this feature will reduce memory usage by decreasing the size of the linear memory, particularly when the `memory.grow` opcode is not used and memory usage is somewhat predictable.
|
||||
|
||||
+## **Instruction metering**
|
||||
+- **WAMR_BUILD_INSTRUCTION_METERING**=1/0, default to disable if not set
|
||||
+> Note: Enabling this feature allows limiting the number of instructions a wasm module instance can execute. Use the `wasm_runtime_set_instruction_count_limit(...)` API before calling `wasm_runtime_call_*(...)` APIs to enforce this limit.
|
||||
+
|
||||
## **Combination of configurations:**
|
||||
|
||||
We can combine the configurations. For example, if we want to disable interpreter, enable AOT and WASI, we can run command:
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_PROTOCOL_BOOK_H_INCLUDED
|
||||
|
||||
#include <xrpl/basics/CountedObject.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
|
||||
#include <boost/utility/base_from_member.hpp>
|
||||
@@ -36,12 +37,17 @@ class Book final : public CountedObject<Book>
|
||||
public:
|
||||
Issue in;
|
||||
Issue out;
|
||||
std::optional<uint256> domain;
|
||||
|
||||
Book()
|
||||
{
|
||||
}
|
||||
|
||||
Book(Issue const& in_, Issue const& out_) : in(in_), out(out_)
|
||||
Book(
|
||||
Issue const& in_,
|
||||
Issue const& out_,
|
||||
std::optional<uint256> const& domain_)
|
||||
: in(in_), out(out_), domain(domain_)
|
||||
{
|
||||
}
|
||||
};
|
||||
@@ -61,6 +67,8 @@ hash_append(Hasher& h, Book const& b)
|
||||
{
|
||||
using beast::hash_append;
|
||||
hash_append(h, b.in, b.out);
|
||||
if (b.domain)
|
||||
hash_append(h, *(b.domain));
|
||||
}
|
||||
|
||||
Book
|
||||
@@ -71,7 +79,8 @@ reversed(Book const& book);
|
||||
[[nodiscard]] inline constexpr bool
|
||||
operator==(Book const& lhs, Book const& rhs)
|
||||
{
|
||||
return (lhs.in == rhs.in) && (lhs.out == rhs.out);
|
||||
return (lhs.in == rhs.in) && (lhs.out == rhs.out) &&
|
||||
(lhs.domain == rhs.domain);
|
||||
}
|
||||
/** @} */
|
||||
|
||||
@@ -82,7 +91,18 @@ operator<=>(Book const& lhs, Book const& rhs)
|
||||
{
|
||||
if (auto const c{lhs.in <=> rhs.in}; c != 0)
|
||||
return c;
|
||||
return lhs.out <=> rhs.out;
|
||||
if (auto const c{lhs.out <=> rhs.out}; c != 0)
|
||||
return c;
|
||||
|
||||
// Manually compare optionals
|
||||
if (lhs.domain && rhs.domain)
|
||||
return *lhs.domain <=> *rhs.domain; // Compare values if both exist
|
||||
if (!lhs.domain && rhs.domain)
|
||||
return std::weak_ordering::less; // Empty is considered less
|
||||
if (lhs.domain && !rhs.domain)
|
||||
return std::weak_ordering::greater; // Non-empty is greater
|
||||
|
||||
return std::weak_ordering::equivalent; // Both are empty
|
||||
}
|
||||
/** @} */
|
||||
|
||||
@@ -126,9 +146,11 @@ template <>
|
||||
struct hash<ripple::Book>
|
||||
{
|
||||
private:
|
||||
using hasher = std::hash<ripple::Issue>;
|
||||
using issue_hasher = std::hash<ripple::Issue>;
|
||||
using uint256_hasher = ripple::uint256::hasher;
|
||||
|
||||
hasher m_hasher;
|
||||
issue_hasher m_issue_hasher;
|
||||
uint256_hasher m_uint256_hasher;
|
||||
|
||||
public:
|
||||
hash() = default;
|
||||
@@ -139,8 +161,12 @@ public:
|
||||
value_type
|
||||
operator()(argument_type const& value) const
|
||||
{
|
||||
value_type result(m_hasher(value.in));
|
||||
boost::hash_combine(result, m_hasher(value.out));
|
||||
value_type result(m_issue_hasher(value.in));
|
||||
boost::hash_combine(result, m_issue_hasher(value.out));
|
||||
|
||||
if (value.domain)
|
||||
boost::hash_combine(result, m_uint256_hasher(*value.domain));
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -154,7 +154,10 @@ enum error_code_i {
|
||||
// Simulate
|
||||
rpcTX_SIGNED = 96,
|
||||
|
||||
rpcLAST = rpcTX_SIGNED // rpcLAST should always equal the last code.
|
||||
// Pathfinding
|
||||
rpcDOMAIN_MALFORMED = 97,
|
||||
|
||||
rpcLAST = rpcDOMAIN_MALFORMED // rpcLAST should always equal the last code.
|
||||
};
|
||||
|
||||
/** Codes returned in the `warnings` array of certain RPC commands.
|
||||
|
||||
@@ -451,8 +451,8 @@ mulDivU(Source1 value, Dest mul, Source2 div)
|
||||
}
|
||||
|
||||
using namespace boost::multiprecision;
|
||||
using uint128 = boost::multiprecision::uint128_t;
|
||||
uint128 product;
|
||||
|
||||
uint128_t product;
|
||||
product = multiply(
|
||||
product,
|
||||
static_cast<std::uint64_t>(value.value()),
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
|
||||
|
||||
/** Reflects the fee settings for a particular ledger.
|
||||
|
||||
The fees are always the same for any transactions applied
|
||||
@@ -36,10 +34,6 @@ struct Fees
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
std::uint32_t extensionComputeLimit{
|
||||
0}; // Extension compute limit (instructions)
|
||||
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
|
||||
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
|
||||
@@ -98,6 +98,12 @@ public:
|
||||
|
||||
static IOUAmount
|
||||
minPositiveAmount();
|
||||
|
||||
friend std::ostream&
|
||||
operator<<(std::ostream& os, IOUAmount const& x)
|
||||
{
|
||||
return os << to_string(x);
|
||||
}
|
||||
};
|
||||
|
||||
inline IOUAmount::IOUAmount(beast::Zero)
|
||||
|
||||
@@ -230,12 +230,6 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
escrow(uint256 const& key) noexcept
|
||||
{
|
||||
return {ltESCROW, key};
|
||||
}
|
||||
|
||||
/** A PaymentChannel */
|
||||
Keylet
|
||||
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||
|
||||
@@ -145,13 +145,15 @@ enum LedgerSpecificFlags {
|
||||
0x10000000, // True, reject new paychans
|
||||
lsfDisallowIncomingTrustline =
|
||||
0x20000000, // True, reject new trustlines (only if no issued assets)
|
||||
// 0x40000000 is available
|
||||
lsfAllowTrustLineLocking =
|
||||
0x40000000, // True, enable trustline locking
|
||||
lsfAllowTrustLineClawback =
|
||||
0x80000000, // True, enable clawback
|
||||
|
||||
// ltOFFER
|
||||
lsfPassive = 0x00010000,
|
||||
lsfSell = 0x00020000, // True, offer was placed as a sell.
|
||||
lsfHybrid = 0x00040000, // True, offer is hybrid.
|
||||
|
||||
// ltRIPPLE_STATE
|
||||
lsfLowReserve = 0x00010000, // True, if entry counts toward reserve.
|
||||
|
||||
@@ -149,12 +149,11 @@ mulRatio(
|
||||
bool roundUp)
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
using int128 = boost::multiprecision::int128_t;
|
||||
|
||||
if (!den)
|
||||
Throw<std::runtime_error>("division by zero");
|
||||
|
||||
int128 const amt128(amt.value());
|
||||
int128_t const amt128(amt.value());
|
||||
auto const neg = amt.value() < 0;
|
||||
auto const m = amt128 * num;
|
||||
auto r = m / den;
|
||||
|
||||
@@ -126,10 +126,6 @@ std::uint8_t constexpr vaultStrategyFirstComeFirstServe = 1;
|
||||
* another vault; counted from 0 */
|
||||
std::uint8_t constexpr maxAssetCheckDepth = 5;
|
||||
|
||||
/** The maximum length of a Data field in Escrow object that can be updated by
|
||||
* Wasm code */
|
||||
std::size_t constexpr maxWasmDataLength = 4 * 1024;
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
bool
|
||||
isFeatureEnabled(uint256 const& feature);
|
||||
|
||||
class DigestAwareReadView;
|
||||
|
||||
/** Rules controlling protocol behavior. */
|
||||
|
||||
@@ -703,6 +703,12 @@ isXRP(STAmount const& amount)
|
||||
return amount.native();
|
||||
}
|
||||
|
||||
bool
|
||||
canAdd(STAmount const& amt1, STAmount const& amt2);
|
||||
|
||||
bool
|
||||
canSubtract(STAmount const& amt1, STAmount const& amt2);
|
||||
|
||||
// Since `canonicalize` does not have access to a ledger, this is needed to put
|
||||
// the low-level routine stAmountCanonicalize on an amendment switch. Only
|
||||
// transactions need to use this switchover. Outside of a transaction it's safe
|
||||
|
||||
@@ -185,8 +185,6 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefNO_TICKET,
|
||||
tefNFTOKEN_IS_NOT_TRANSFERABLE,
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_WASM,
|
||||
tefWASM_FIELD_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -362,7 +360,8 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecWRONG_ASSET = 194,
|
||||
tecLIMIT_EXCEEDED = 195,
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecWASM_REJECTED = 197,
|
||||
tecPRECISION_LOSS = 197,
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -92,15 +92,16 @@ constexpr std::uint32_t asfDisallowIncomingCheck = 13;
|
||||
constexpr std::uint32_t asfDisallowIncomingPayChan = 14;
|
||||
constexpr std::uint32_t asfDisallowIncomingTrustline = 15;
|
||||
constexpr std::uint32_t asfAllowTrustLineClawback = 16;
|
||||
constexpr std::uint32_t asfAllowTrustLineLocking = 17;
|
||||
|
||||
// OfferCreate flags:
|
||||
constexpr std::uint32_t tfPassive = 0x00010000;
|
||||
constexpr std::uint32_t tfImmediateOrCancel = 0x00020000;
|
||||
constexpr std::uint32_t tfFillOrKill = 0x00040000;
|
||||
constexpr std::uint32_t tfSell = 0x00080000;
|
||||
|
||||
constexpr std::uint32_t tfHybrid = 0x00100000;
|
||||
constexpr std::uint32_t tfOfferCreateMask =
|
||||
~(tfUniversal | tfPassive | tfImmediateOrCancel | tfFillOrKill | tfSell);
|
||||
~(tfUniversal | tfPassive | tfImmediateOrCancel | tfFillOrKill | tfSell | tfHybrid);
|
||||
|
||||
// Payment flags:
|
||||
constexpr std::uint32_t tfNoRippleDirect = 0x00010000;
|
||||
@@ -121,13 +122,7 @@ constexpr std::uint32_t tfClearDeepFreeze = 0x00800000;
|
||||
constexpr std::uint32_t tfTrustSetMask =
|
||||
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
|
||||
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
|
||||
|
||||
// valid flags for granular permission
|
||||
constexpr std::uint32_t tfTrustSetGranularMask = tfSetfAuth | tfSetFreeze | tfClearFreeze;
|
||||
|
||||
// bits representing supportedGranularMask are set to 0 and the bits
|
||||
// representing other flags are set to 1 in tfPermissionMask.
|
||||
constexpr std::uint32_t tfTrustSetPermissionMask = (~tfTrustSetMask) & (~tfTrustSetGranularMask);
|
||||
constexpr std::uint32_t tfTrustSetPermissionMask = ~(tfUniversal | tfSetfAuth | tfSetFreeze | tfClearFreeze);
|
||||
|
||||
// EnableAmendment flags:
|
||||
constexpr std::uint32_t tfGotMajority = 0x00010000;
|
||||
@@ -164,8 +159,7 @@ constexpr std::uint32_t const tfMPTokenAuthorizeMask = ~(tfUniversal | tfMPTUna
|
||||
constexpr std::uint32_t const tfMPTLock = 0x00000001;
|
||||
constexpr std::uint32_t const tfMPTUnlock = 0x00000002;
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock);
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetGranularMask = tfMPTLock | tfMPTUnlock;
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = (~tfMPTokenIssuanceSetMask) & (~tfMPTokenIssuanceSetGranularMask);
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceSetPermissionMask = ~(tfUniversal | tfMPTLock | tfMPTUnlock);
|
||||
|
||||
// MPTokenIssuanceDestroy flags:
|
||||
constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal;
|
||||
|
||||
@@ -46,7 +46,10 @@ private:
|
||||
CtorHelper);
|
||||
|
||||
public:
|
||||
TxMeta(uint256 const& transactionID, std::uint32_t ledger);
|
||||
TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId = std::nullopt);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&);
|
||||
@@ -133,7 +136,7 @@ public:
|
||||
void
|
||||
setParentBatchId(uint256 const& parentBatchId)
|
||||
{
|
||||
parentBatchId_ = parentBatchId;
|
||||
mParentBatchId = parentBatchId;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -142,33 +145,13 @@ public:
|
||||
XRPL_ASSERT(
|
||||
hasParentBatchId(),
|
||||
"ripple::TxMeta::getParentBatchId : non-null batch id");
|
||||
return *parentBatchId_;
|
||||
return *mParentBatchId;
|
||||
}
|
||||
|
||||
bool
|
||||
hasParentBatchId() const
|
||||
{
|
||||
return static_cast<bool>(parentBatchId_);
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::uint32_t const& gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getGasUsed() const
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hasGasUsed(), "ripple::TxMeta::getGasUsed : non-null batch id");
|
||||
return *gasUsed_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasGasUsed() const
|
||||
{
|
||||
return static_cast<bool>(gasUsed_);
|
||||
return static_cast<bool>(mParentBatchId);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -178,8 +161,7 @@ private:
|
||||
int mResult;
|
||||
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<uint256> parentBatchId_;
|
||||
std::optional<uint256> mParentBatchId;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -63,6 +63,9 @@ using NodeID = base_uint<160, detail::NodeIDTag>;
|
||||
* and a 160-bit account */
|
||||
using MPTID = base_uint<192>;
|
||||
|
||||
/** Domain is a 256-bit hash representing a specific domain. */
|
||||
using Domain = base_uint<256>;
|
||||
|
||||
/** XRP currency. */
|
||||
Currency const&
|
||||
xrpCurrency();
|
||||
|
||||
@@ -286,12 +286,11 @@ mulRatio(
|
||||
bool roundUp)
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
using int128 = boost::multiprecision::int128_t;
|
||||
|
||||
if (!den)
|
||||
Throw<std::runtime_error>("division by zero");
|
||||
|
||||
int128 const amt128(amt.drops());
|
||||
int128_t const amt128(amt.drops());
|
||||
auto const neg = amt.drops() < 0;
|
||||
auto const m = amt128 * num;
|
||||
auto r = m / den;
|
||||
|
||||
@@ -32,11 +32,15 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -188,6 +188,7 @@ LEDGER_ENTRY(ltDIR_NODE, 0x0064, DirectoryNode, directory, ({
|
||||
{sfNFTokenID, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL}
|
||||
}))
|
||||
|
||||
/** The ledger object which lists details about amendments on the network.
|
||||
@@ -249,6 +250,8 @@ LEDGER_ENTRY(ltOFFER, 0x006f, Offer, offer, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
{sfAdditionalBooks, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object which describes a deposit preauthorization.
|
||||
@@ -316,12 +319,6 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
|
||||
// New fields
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
|
||||
{sfPreviousTxnID, soeOPTIONAL},
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
@@ -351,14 +348,14 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
{sfSourceTag, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfDestinationNode, soeOPTIONAL},
|
||||
{sfTransferRate, soeOPTIONAL},
|
||||
{sfIssuerNode, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object describing a single unidirectional XRP payment channel.
|
||||
@@ -410,6 +407,7 @@ LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, mpt_issuance, ({
|
||||
{sfAssetScale, soeDEFAULT},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
{sfOutstandingAmount, soeREQUIRED},
|
||||
{sfLockedAmount, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
@@ -423,6 +421,7 @@ LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, mptoken, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeDEFAULT},
|
||||
{sfLockedAmount, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
|
||||
@@ -114,11 +114,6 @@ TYPED_SFIELD(sfVoteWeight, UINT32, 48)
|
||||
TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 53)
|
||||
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 54)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 55)
|
||||
TYPED_SFIELD(sfComputationAllowance, UINT32, 56)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 57)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -149,6 +144,7 @@ TYPED_SFIELD(sfOutstandingAmount, UINT64, 25, SField::sMD_BaseTen|SFie
|
||||
TYPED_SFIELD(sfMPTAmount, UINT64, 26, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
TYPED_SFIELD(sfIssuerNode, UINT64, 27)
|
||||
TYPED_SFIELD(sfSubjectNode, UINT64, 28)
|
||||
TYPED_SFIELD(sfLockedAmount, UINT64, 29, SField::sMD_BaseTen|SField::sMD_Default)
|
||||
|
||||
// 128-bit
|
||||
TYPED_SFIELD(sfEmailHash, UINT128, 1)
|
||||
@@ -237,7 +233,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
||||
|
||||
// currency amount (more)
|
||||
// currency amount (AMM)
|
||||
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||
@@ -279,7 +275,6 @@ TYPED_SFIELD(sfAssetClass, VL, 28)
|
||||
TYPED_SFIELD(sfProvider, VL, 29)
|
||||
TYPED_SFIELD(sfMPTokenMetadata, VL, 30)
|
||||
TYPED_SFIELD(sfCredentialType, VL, 31)
|
||||
TYPED_SFIELD(sfFinishFunction, VL, 32)
|
||||
|
||||
// account (common)
|
||||
TYPED_SFIELD(sfAccount, ACCOUNT, 1)
|
||||
@@ -366,6 +361,7 @@ UNTYPED_SFIELD(sfPriceData, OBJECT, 32)
|
||||
UNTYPED_SFIELD(sfCredential, OBJECT, 33)
|
||||
UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34)
|
||||
UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35)
|
||||
UNTYPED_SFIELD(sfBook, OBJECT, 36)
|
||||
|
||||
// array of objects (common)
|
||||
// ARRAY/1 is reserved for end of array
|
||||
@@ -381,6 +377,7 @@ UNTYPED_SFIELD(sfMemos, ARRAY, 9)
|
||||
UNTYPED_SFIELD(sfNFTokens, ARRAY, 10)
|
||||
UNTYPED_SFIELD(sfHooks, ARRAY, 11)
|
||||
UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12)
|
||||
UNTYPED_SFIELD(sfAdditionalBooks, ARRAY, 13)
|
||||
|
||||
// array of objects (uncommon)
|
||||
UNTYPED_SFIELD(sfMajorities, ARRAY, 16)
|
||||
|
||||
@@ -38,18 +38,17 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfDeliverMin, soeOPTIONAL, soeMPTSupported},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type creates an escrow object. */
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -59,7 +58,6 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfComputationAllowance, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
|
||||
@@ -96,6 +94,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::delegatable, ({
|
||||
{sfTakerGets, soeREQUIRED},
|
||||
{sfExpiration, soeOPTIONAL},
|
||||
{sfOfferSequence, soeOPTIONAL},
|
||||
{sfDomainID, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type cancels existing offers to trade one asset for another. */
|
||||
@@ -546,9 +545,6 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -268,9 +268,6 @@ JSS(expected_date_UTC); // out: any (warnings)
|
||||
JSS(expected_ledger_size); // out: TxQ
|
||||
JSS(expiration); // out: AccountOffers, AccountChannels,
|
||||
// ValidatorList, amm_info
|
||||
JSS(extension_compute); // out: NetworkOps
|
||||
JSS(extension_size); // out: NetworkOps
|
||||
JSS(gas_price); // out: NetworkOps
|
||||
JSS(fail_hard); // in: Sign, Submit
|
||||
JSS(failed); // out: InboundLedger
|
||||
JSS(feature); // in: Feature
|
||||
@@ -394,6 +391,7 @@ JSS(load_fee); // out: LoadFeeTrackImp, NetworkOPs
|
||||
JSS(local); // out: resource/Logic.h
|
||||
JSS(local_txs); // out: GetCounts
|
||||
JSS(local_static_keys); // out: ValidatorList
|
||||
JSS(locked); // out: GatewayBalances
|
||||
JSS(low); // out: BookChanges
|
||||
JSS(lowest_sequence); // out: AccountInfo
|
||||
JSS(lowest_ticket); // out: AccountInfo
|
||||
|
||||
@@ -48,7 +48,7 @@ operator<<(std::ostream& os, Book const& x)
|
||||
Book
|
||||
reversed(Book const& book)
|
||||
{
|
||||
return Book(book.out, book.in);
|
||||
return Book(book.out, book.in, book.domain);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -36,7 +36,7 @@ namespace BuildInfo {
|
||||
// and follow the format described at http://semver.org/
|
||||
//------------------------------------------------------------------------------
|
||||
// clang-format off
|
||||
char const* const versionString = "2.5.0-b1"
|
||||
char const* const versionString = "2.5.0-rc2"
|
||||
// clang-format on
|
||||
|
||||
#if defined(DEBUG) || defined(SANITIZER)
|
||||
|
||||
@@ -116,7 +116,8 @@ constexpr static ErrorInfo unorderedErrorInfos[]{
|
||||
{rpcUNKNOWN_COMMAND, "unknownCmd", "Unknown method.", 405},
|
||||
{rpcORACLE_MALFORMED, "oracleMalformed", "Oracle request is malformed.", 400},
|
||||
{rpcBAD_CREDENTIALS, "badCredentials", "Credentials do not exist, are not accepted, or have expired.", 400},
|
||||
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400}};
|
||||
{rpcTX_SIGNED, "transactionSigned", "Transaction should not be signed.", 400},
|
||||
{rpcDOMAIN_MALFORMED, "domainMalformed", "Domain is malformed.", 400}};
|
||||
// clang-format on
|
||||
|
||||
// Sort and validate unorderedErrorInfos at compile time. Should be
|
||||
|
||||
@@ -117,12 +117,19 @@ getBookBase(Book const& book)
|
||||
XRPL_ASSERT(
|
||||
isConsistent(book), "ripple::getBookBase : input is consistent");
|
||||
|
||||
auto const index = indexHash(
|
||||
LedgerNameSpace::BOOK_DIR,
|
||||
book.in.currency,
|
||||
book.out.currency,
|
||||
book.in.account,
|
||||
book.out.account);
|
||||
auto const index = book.domain ? indexHash(
|
||||
LedgerNameSpace::BOOK_DIR,
|
||||
book.in.currency,
|
||||
book.out.currency,
|
||||
book.in.account,
|
||||
book.out.account,
|
||||
*(book.domain))
|
||||
: indexHash(
|
||||
LedgerNameSpace::BOOK_DIR,
|
||||
book.in.currency,
|
||||
book.out.currency,
|
||||
book.in.account,
|
||||
book.out.account);
|
||||
|
||||
// Return with quality 0.
|
||||
auto k = keylet::quality({ltDIR_NODE, index}, 0);
|
||||
|
||||
@@ -165,6 +165,13 @@ InnerObjectFormats::InnerObjectFormats()
|
||||
{sfSigningPubKey, soeOPTIONAL},
|
||||
{sfTxnSignature, soeOPTIONAL},
|
||||
{sfSigners, soeOPTIONAL}});
|
||||
|
||||
add(sfBook.jsonName,
|
||||
sfBook.getCode(),
|
||||
{
|
||||
{sfBookDirectory, soeREQUIRED},
|
||||
{sfBookNode, soeREQUIRED},
|
||||
});
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
|
||||
@@ -161,4 +161,12 @@ Rules::operator!=(Rules const& other) const
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool
|
||||
isFeatureEnabled(uint256 const& feature)
|
||||
{
|
||||
auto const& rules = getCurrentTransactionRules();
|
||||
return rules && rules->enabled(feature);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -506,6 +506,162 @@ getRate(STAmount const& offerOut, STAmount const& offerIn)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Safely checks if two STAmount values can be added without overflow,
|
||||
* underflow, or precision loss.
|
||||
*
|
||||
* This function determines whether the addition of two STAmount objects is
|
||||
* safe, depending on their type:
|
||||
* - For XRP amounts, it checks for integer overflow and underflow.
|
||||
* - For IOU amounts, it checks for acceptable precision loss.
|
||||
* - For MPT amounts, it checks for overflow and underflow within 63-bit signed
|
||||
* integer limits.
|
||||
* - If either amount is zero, addition is always considered safe.
|
||||
* - If the amounts are of different currencies or types, addition is not
|
||||
* allowed.
|
||||
*
|
||||
* @param a The first STAmount to add.
|
||||
* @param b The second STAmount to add.
|
||||
* @return true if the addition is safe; false otherwise.
|
||||
*/
|
||||
bool
|
||||
canAdd(STAmount const& a, STAmount const& b)
|
||||
{
|
||||
// cannot add different currencies
|
||||
if (!areComparable(a, b))
|
||||
return false;
|
||||
|
||||
// special case: adding anything to zero is always fine
|
||||
if (a == beast::zero || b == beast::zero)
|
||||
return true;
|
||||
|
||||
// XRP case (overflow & underflow check)
|
||||
if (isXRP(a) && isXRP(b))
|
||||
{
|
||||
XRPAmount A = a.xrp();
|
||||
XRPAmount B = b.xrp();
|
||||
|
||||
if ((B > XRPAmount{0} &&
|
||||
A > XRPAmount{std::numeric_limits<XRPAmount::value_type>::max()} -
|
||||
B) ||
|
||||
(B < XRPAmount{0} &&
|
||||
A < XRPAmount{std::numeric_limits<XRPAmount::value_type>::min()} -
|
||||
B))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// IOU case (precision check)
|
||||
if (a.holds<Issue>() && b.holds<Issue>())
|
||||
{
|
||||
static STAmount const one{IOUAmount{1, 0}, noIssue()};
|
||||
static STAmount const maxLoss{IOUAmount{1, -4}, noIssue()};
|
||||
STAmount lhs = divide((a - b) + b, a, noIssue()) - one;
|
||||
STAmount rhs = divide((b - a) + a, b, noIssue()) - one;
|
||||
return ((rhs.negative() ? -rhs : rhs) +
|
||||
(lhs.negative() ? -lhs : lhs)) <= maxLoss;
|
||||
}
|
||||
|
||||
// MPT (overflow & underflow check)
|
||||
if (a.holds<MPTIssue>() && b.holds<MPTIssue>())
|
||||
{
|
||||
MPTAmount A = a.mpt();
|
||||
MPTAmount B = b.mpt();
|
||||
if ((B > MPTAmount{0} &&
|
||||
A > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} -
|
||||
B) ||
|
||||
(B < MPTAmount{0} &&
|
||||
A < MPTAmount{std::numeric_limits<MPTAmount::value_type>::min()} -
|
||||
B))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("STAmount::canAdd : unexpected STAmount type");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Determines if it is safe to subtract one STAmount from another.
|
||||
*
|
||||
* This function checks whether subtracting amount `b` from amount `a` is valid,
|
||||
* considering currency compatibility and underflow conditions for specific
|
||||
* types.
|
||||
*
|
||||
* - Subtracting zero is always allowed.
|
||||
* - Subtraction is only allowed between comparable currencies.
|
||||
* - For XRP amounts, ensures no underflow or overflow occurs.
|
||||
* - For IOU amounts, subtraction is always allowed (no underflow).
|
||||
* - For MPT amounts, ensures no underflow or overflow occurs.
|
||||
*
|
||||
* @param a The minuend (amount to subtract from).
|
||||
* @param b The subtrahend (amount to subtract).
|
||||
* @return true if subtraction is allowed, false otherwise.
|
||||
*/
|
||||
bool
|
||||
canSubtract(STAmount const& a, STAmount const& b)
|
||||
{
|
||||
// Cannot subtract different currencies
|
||||
if (!areComparable(a, b))
|
||||
return false;
|
||||
|
||||
// Special case: subtracting zero is always fine
|
||||
if (b == beast::zero)
|
||||
return true;
|
||||
|
||||
// XRP case (underflow & overflow check)
|
||||
if (isXRP(a) && isXRP(b))
|
||||
{
|
||||
XRPAmount A = a.xrp();
|
||||
XRPAmount B = b.xrp();
|
||||
// Check for underflow
|
||||
if (B > XRPAmount{0} && A < B)
|
||||
return false;
|
||||
|
||||
// Check for overflow
|
||||
if (B < XRPAmount{0} &&
|
||||
A > XRPAmount{std::numeric_limits<XRPAmount::value_type>::max()} +
|
||||
B)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// IOU case (no underflow)
|
||||
if (a.holds<Issue>() && b.holds<Issue>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// MPT case (underflow & overflow check)
|
||||
if (a.holds<MPTIssue>() && b.holds<MPTIssue>())
|
||||
{
|
||||
MPTAmount A = a.mpt();
|
||||
MPTAmount B = b.mpt();
|
||||
|
||||
// Underflow check
|
||||
if (B > MPTAmount{0} && A < B)
|
||||
return false;
|
||||
|
||||
// Overflow check
|
||||
if (B < MPTAmount{0} &&
|
||||
A > MPTAmount{std::numeric_limits<MPTAmount::value_type>::max()} +
|
||||
B)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
UNREACHABLE("STAmount::canSubtract : unexpected STAmount type");
|
||||
return false;
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
void
|
||||
STAmount::setJson(Json::Value& elem) const
|
||||
{
|
||||
|
||||
@@ -1052,7 +1052,8 @@ parseArray(
|
||||
Json::Value const objectFields(json[i][objectName]);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << json_name << "." << "[" << i << "]." << objectName;
|
||||
ss << json_name << "."
|
||||
<< "[" << i << "]." << objectName;
|
||||
|
||||
auto ret = parseObject(
|
||||
ss.str(), objectFields, nameField, depth + 1, error);
|
||||
|
||||
@@ -78,10 +78,6 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -126,7 +126,8 @@ transResults()
|
||||
MAKE_ERROR(tecWRONG_ASSET, "Wrong asset given."),
|
||||
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -150,8 +151,6 @@ transResults()
|
||||
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
|
||||
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
|
||||
@@ -56,10 +56,9 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
setParentBatchId(obj.getFieldH256(sfParentBatchID));
|
||||
if (obj.isFieldPresent(sfGasUsed))
|
||||
setGasUsed(obj.getFieldU32(sfGasUsed));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
@@ -83,9 +82,6 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
setParentBatchId(obj.getFieldH256(sfParentBatchID));
|
||||
|
||||
if (obj.isFieldPresent(sfGasUsed))
|
||||
setGasUsed(obj.getFieldU32(sfGasUsed));
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
|
||||
@@ -101,11 +97,15 @@ TxMeta::TxMeta(
|
||||
{
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
|
||||
TxMeta::TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId)
|
||||
: mTransactionID(transactionID)
|
||||
, mLedger(ledger)
|
||||
, mIndex(static_cast<std::uint32_t>(-1))
|
||||
, mResult(255)
|
||||
, mParentBatchId(parentBatchId)
|
||||
, mNodes(sfAffectedNodes)
|
||||
{
|
||||
mNodes.reserve(32);
|
||||
@@ -241,10 +241,9 @@ TxMeta::getAsObject() const
|
||||
metaData.emplace_back(mNodes);
|
||||
if (hasDeliveredAmount())
|
||||
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
|
||||
|
||||
if (hasParentBatchId())
|
||||
metaData.setFieldH256(sfParentBatchID, getParentBatchId());
|
||||
if (hasGasUsed())
|
||||
metaData.setFieldU32(sfGasUsed, getGasUsed());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
+404
-139
@@ -581,8 +581,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
|
||||
|
||||
// gw clawback 1000 USD from the AMM pool
|
||||
env(amm::ammClawback(gw, alice, USD, EUR, USD(1000)),
|
||||
@@ -601,12 +605,20 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// 1000 USD and 1250 EUR was withdrawn from the AMM pool, so the
|
||||
// current balance is 3000 USD and 3750 EUR.
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(3000), EUR(3750), IOUAmount{3354101966249685, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(3000), EUR(3750), IOUAmount{3354101966249684, -12}));
|
||||
|
||||
// Alice has 3/4 of its initial lptokens Left.
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{3354101966249685, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{3354101966249685, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{3354101966249684, -12}));
|
||||
|
||||
// gw clawback another 500 USD from the AMM pool.
|
||||
env(amm::ammClawback(gw, alice, USD, EUR, USD(500)),
|
||||
@@ -617,14 +629,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// AMM pool.
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2500000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(3125000000000001), -12},
|
||||
IOUAmount{2795084971874738, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2500000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(3125000000000001), -12},
|
||||
IOUAmount{2795084971874738, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), EUR(3125), IOUAmount{2795084971874737, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, EUR) ==
|
||||
STAmount(EUR, UINT64_C(2874999999999999), -12));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, EUR) ==
|
||||
STAmount(EUR, UINT64_C(2874999999999999), -12));
|
||||
else
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(2875));
|
||||
|
||||
// gw clawback small amount, 1 USD.
|
||||
env(amm::ammClawback(gw, alice, USD, EUR, USD(1)), ter(tesSUCCESS));
|
||||
@@ -633,14 +652,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// Another 1 USD / 1.25 EUR was withdrawn.
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2499000000000002), -12},
|
||||
STAmount{EUR, UINT64_C(3123750000000002), -12},
|
||||
IOUAmount{2793966937885989, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2499000000000002), -12},
|
||||
STAmount{EUR, UINT64_C(3123750000000002), -12},
|
||||
IOUAmount{2793966937885989, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2499), EUR(3123.75), IOUAmount{2793966937885987, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, EUR) ==
|
||||
STAmount(EUR, UINT64_C(2876249999999998), -12));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
env.balance(alice, EUR) ==
|
||||
STAmount(EUR, UINT64_C(2'876'249999999998), -12));
|
||||
else
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(2876.25));
|
||||
|
||||
// gw clawback 4000 USD, exceeding the current balance. We
|
||||
// will clawback all.
|
||||
@@ -713,14 +739,26 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// gw2 creates AMM pool of XRP/EUR, alice and bob deposit XRP/EUR.
|
||||
AMM amm2(env, gw2, XRP(3000), EUR(1000), ter(tesSUCCESS));
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
|
||||
amm2.deposit(alice, EUR(1000), XRP(3000));
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
|
||||
amm2.deposit(bob, EUR(1000), XRP(3000));
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(3000), XRP(9000), IOUAmount{5196152422706634, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(3000), XRP(9000), IOUAmount{5196152422706631, -9}));
|
||||
env.close();
|
||||
|
||||
auto aliceXrpBalance = env.balance(alice, XRP);
|
||||
@@ -743,10 +781,18 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(1000)));
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), XRP(5000), IOUAmount{3535533905932738, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), XRP(5000), IOUAmount{3535533905932737, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865480, -10}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865474, -10}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1414213562373095, -9}));
|
||||
|
||||
@@ -760,14 +806,26 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// Bob gets 20 XRP back.
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(20)));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2490000000000001), -12},
|
||||
XRP(4980),
|
||||
IOUAmount{3521391770309008, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{7071067811865480, -10}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(2490000000000001), -12},
|
||||
XRP(4980),
|
||||
IOUAmount{3521391770309008, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2'490), XRP(4980), IOUAmount{3521391770309006, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865480, -10}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{7071067811865474, -10}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
|
||||
|
||||
// gw2 clawback 200 EUR from amm2.
|
||||
env(amm::ammClawback(gw2, alice, EUR, XRP, EUR(200)),
|
||||
@@ -780,12 +838,24 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// Alice gets 600 XRP back.
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(1000) + XRP(600)));
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(alice, IOUAmount{1385640646055103, -9}));
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2800), XRP(8400), IOUAmount{4849742261192859, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2800), XRP(8400), IOUAmount{4849742261192856, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectLPTokens(
|
||||
alice, IOUAmount{1385640646055103, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectLPTokens(
|
||||
alice, IOUAmount{1385640646055102, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(bob, IOUAmount{1732050807568878, -9}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm2.expectLPTokens(bob, IOUAmount{1732050807568877, -9}));
|
||||
|
||||
// gw claw back 1000 USD from alice in amm, which exceeds alice's
|
||||
// balance. This will clawback all the remaining LP tokens of alice
|
||||
@@ -798,17 +868,34 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env.require(balance(bob, gw["USD"](4000)));
|
||||
|
||||
// Alice gets 1000 XRP back.
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
|
||||
XRPAmount{1}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(1990000000000001), -12},
|
||||
XRP(3980),
|
||||
IOUAmount{2814284989122460, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749365, -9}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{1400071426749364, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(1990000000000001), -12},
|
||||
XRP(3980),
|
||||
IOUAmount{2814284989122460, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(1'990),
|
||||
XRPAmount{3'980'000'001},
|
||||
IOUAmount{2814284989122459, -9}));
|
||||
|
||||
// gw clawback 1000 USD from bob in amm, which also exceeds bob's
|
||||
// balance in amm. All bob's lptoken in amm will be consumed, which
|
||||
@@ -820,10 +907,17 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env.require(balance(alice, gw["USD"](5000)));
|
||||
env.require(balance(bob, gw["USD"](4000)));
|
||||
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) -
|
||||
XRPAmount{1}));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
|
||||
|
||||
@@ -843,21 +937,32 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// Alice gets another 2400 XRP back, bob's XRP balance remains the
|
||||
// same.
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400) - XRPAmount{1}));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, bob, bobXrpBalance + XRP(20) + XRP(1980)));
|
||||
|
||||
// Alice now does not have any lptoken in amm2
|
||||
BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
|
||||
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137756, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(2000), XRP(6000), IOUAmount{3464101615137754, -9}));
|
||||
|
||||
// gw2 claw back 2000 EUR from bib in amm2, which exceeds bob's
|
||||
// gw2 claw back 2000 EUR from bob in amm2, which exceeds bob's
|
||||
// balance. All bob's lptokens will be consumed, which corresponds
|
||||
// to 1000EUR / 3000 XRP.
|
||||
env(amm::ammClawback(gw2, bob, EUR, XRP, EUR(2000)),
|
||||
@@ -869,11 +974,18 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// Bob gets another 3000 XRP back. Alice's XRP balance remains the
|
||||
// same.
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env,
|
||||
alice,
|
||||
aliceXrpBalance + XRP(1000) + XRP(600) + XRP(1000) +
|
||||
XRP(2400) - XRPAmount{1}));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, bob, bobXrpBalance + XRP(20) + XRP(1980) + XRP(3000)));
|
||||
|
||||
@@ -881,8 +993,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(amm2.expectLPTokens(alice, IOUAmount(0)));
|
||||
BEAST_EXPECT(amm2.expectLPTokens(bob, IOUAmount(0)));
|
||||
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568878, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm2.expectBalances(
|
||||
EUR(1000), XRP(3000), IOUAmount{1732050807568877, -9}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,21 +1056,45 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
AMM amm(env, alice, EUR(5000), USD(4000), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4000), EUR(5000), IOUAmount{4472135954999579, -12}));
|
||||
amm.deposit(bob, USD(2000), EUR(2500));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(6000), EUR(7500), IOUAmount{6708203932499370, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(6000), EUR(7500), IOUAmount{6708203932499368, -12}));
|
||||
amm.deposit(carol, USD(1000), EUR(1250));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(7000), EUR(8750), IOUAmount{7826237921249265, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(7000), EUR(8750), IOUAmount{7826237921249262, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999579, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{2236067977499790, -12}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(bob, IOUAmount{2236067977499789, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749895, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749894, -12}));
|
||||
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
env.require(balance(alice, gw2["EUR"](1000)));
|
||||
@@ -968,16 +1108,30 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(4999999999999999), -12},
|
||||
STAmount{EUR, UINT64_C(6249999999999999), -12},
|
||||
IOUAmount{5590169943749475, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(4999999999999999), -12},
|
||||
STAmount{EUR, UINT64_C(6249999999999999), -12},
|
||||
IOUAmount{5590169943749475, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(5000000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(6250000000000001), -12},
|
||||
IOUAmount{5590169943749473, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999579, -12}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(carol, IOUAmount{1118033988749895, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749895, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
carol, IOUAmount{1118033988749894, -12}));
|
||||
|
||||
// Bob will get 2500 EUR back.
|
||||
env.require(balance(alice, gw["USD"](2000)));
|
||||
@@ -986,9 +1140,14 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env.balance(bob, USD) ==
|
||||
STAmount(USD, UINT64_C(3000000000000000), -12));
|
||||
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, EUR) ==
|
||||
STAmount(EUR, UINT64_C(5000000000000001), -12));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, EUR) ==
|
||||
STAmount(EUR, UINT64_C(5000000000000001), -12));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(bob, EUR) ==
|
||||
STAmount(EUR, UINT64_C(4999999999999999), -12));
|
||||
env.require(balance(carol, gw["USD"](3000)));
|
||||
env.require(balance(carol, gw2["EUR"](2750)));
|
||||
|
||||
@@ -996,13 +1155,23 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env(amm::ammClawback(gw2, carol, EUR, USD, std::nullopt),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(3999999999999999), -12},
|
||||
STAmount{EUR, UINT64_C(4999999999999999), -12},
|
||||
IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(3999999999999999), -12},
|
||||
STAmount{EUR, UINT64_C(4999999999999999), -12},
|
||||
IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount{USD, UINT64_C(4000000000000001), -12},
|
||||
STAmount{EUR, UINT64_C(5000000000000002), -12},
|
||||
IOUAmount{4472135954999579, -12}));
|
||||
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4472135954999580, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999580, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectLPTokens(
|
||||
alice, IOUAmount{4472135954999579, -12}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(bob, IOUAmount(0)));
|
||||
BEAST_EXPECT(amm.expectLPTokens(carol, IOUAmount(0)));
|
||||
|
||||
@@ -1041,14 +1210,26 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
|
||||
// gw creates AMM pool of XRP/USD, alice and bob deposit XRP/USD.
|
||||
AMM amm(env, gw, XRP(2000), USD(10000), ter(tesSUCCESS));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999579, -9}));
|
||||
amm.deposit(alice, USD(1000), XRP(200));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(11000), XRP(2200), IOUAmount{4919349550499538, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(11000), XRP(2200), IOUAmount{4919349550499536, -9}));
|
||||
amm.deposit(bob, USD(2000), XRP(400));
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(13000), XRP(2600), IOUAmount{5813776741499453, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(13000), XRP(2600), IOUAmount{5813776741499451, -9}));
|
||||
env.close();
|
||||
|
||||
auto aliceXrpBalance = env.balance(alice, XRP);
|
||||
@@ -1058,18 +1239,34 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, alice, aliceXrpBalance + XRP(200)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(12000), XRP(2400), IOUAmount{5366563145999495, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(12000),
|
||||
XRPAmount(2400000001),
|
||||
IOUAmount{5366563145999494, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(200)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(200) - XRPAmount{1}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
|
||||
// gw clawback all bob's USD in amm. (2000 USD / 400 XRP)
|
||||
env(amm::ammClawback(gw, bob, USD, XRP, std::nullopt),
|
||||
ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000), XRP(2000), IOUAmount{4472135954999580, -9}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(10000),
|
||||
XRPAmount(2000000001),
|
||||
IOUAmount{4472135954999579, -9}));
|
||||
BEAST_EXPECT(
|
||||
expectLedgerEntryRoot(env, bob, bobXrpBalance + XRP(400)));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
@@ -1125,10 +1322,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
amm.deposit(bob, USD(4000), EUR(1000));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
|
||||
amm.deposit(carol, USD(2000), EUR(500));
|
||||
if (!features[fixAMMv1_3])
|
||||
amm.deposit(carol, USD(2000), EUR(500));
|
||||
else
|
||||
amm.deposit(carol, USD(2000.25), EUR(500));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
|
||||
|
||||
// gw clawback 1000 USD from carol.
|
||||
env(amm::ammClawback(gw, carol, USD, EUR, USD(1000)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
@@ -1142,7 +1341,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
// 250 EUR goes back to carol.
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
@@ -1164,7 +1368,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
// 250 EUR did not go back to bob because tfClawTwoAssets is set.
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
// gw clawback all USD from alice and set tfClawTwoAssets.
|
||||
@@ -1181,7 +1390,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
}
|
||||
|
||||
@@ -1366,12 +1580,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// gw2 claws back 1000 EUR from gw.
|
||||
env(amm::ammClawback(gw2, gw, EUR, USD, EUR(1000)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4500),
|
||||
STAmount(EUR, UINT64_C(9000000000000001), -12),
|
||||
IOUAmount{6363961030678928, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4500),
|
||||
STAmount(EUR, UINT64_C(9000000000000001), -12),
|
||||
IOUAmount{6363961030678928, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(4500), EUR(9000), IOUAmount{6363961030678928, -12}));
|
||||
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{4242640687119285, -12}));
|
||||
@@ -1384,12 +1607,21 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
// gw2 claws back 4000 EUR from alice.
|
||||
env(amm::ammClawback(gw2, alice, EUR, USD, EUR(4000)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500),
|
||||
STAmount(EUR, UINT64_C(5000000000000001), -12),
|
||||
IOUAmount{3535533905932738, -12}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500),
|
||||
STAmount(EUR, UINT64_C(5000000000000001), -12),
|
||||
IOUAmount{3535533905932738, -12}));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
USD(2500), EUR(5000), IOUAmount{3535533905932738, -12}));
|
||||
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865480, -13}));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(gw, IOUAmount{7071067811865475, -13}));
|
||||
BEAST_EXPECT(amm.expectLPTokens(gw2, IOUAmount{1414213562373095, -12}));
|
||||
BEAST_EXPECT(
|
||||
amm.expectLPTokens(alice, IOUAmount{1414213562373095, -12}));
|
||||
@@ -1653,7 +1885,10 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
amm.deposit(bob, USD(4000), EUR(1000));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(12000), EUR(3000), IOUAmount(6000)));
|
||||
amm.deposit(carol, USD(2000), EUR(500));
|
||||
if (!features[fixAMMv1_3])
|
||||
amm.deposit(carol, USD(2000), EUR(500));
|
||||
else
|
||||
amm.deposit(carol, USD(2000.25), EUR(500));
|
||||
BEAST_EXPECT(
|
||||
amm.expectBalances(USD(14000), EUR(3500), IOUAmount(7000)));
|
||||
|
||||
@@ -1675,7 +1910,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
// 250 EUR goes back to carol.
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
@@ -1697,7 +1937,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
// 250 EUR did not go back to bob because tfClawTwoAssets is set.
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
|
||||
// gw clawback all USD from alice and set tfClawTwoAssets.
|
||||
@@ -1715,7 +1960,12 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
BEAST_EXPECT(env.balance(alice, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(bob, USD) == USD(5000));
|
||||
BEAST_EXPECT(env.balance(bob, EUR) == EUR(8000));
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(env.balance(carol, USD) == USD(6000));
|
||||
else
|
||||
BEAST_EXPECT(
|
||||
env.balance(carol, USD) ==
|
||||
STAmount(USD, UINT64_C(5999'999999999999), -12));
|
||||
BEAST_EXPECT(env.balance(carol, EUR) == EUR(7750));
|
||||
}
|
||||
}
|
||||
@@ -1763,13 +2013,23 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
env(amm::ammClawback(gw, alice, USD, XRP, USD(400)), ter(tesSUCCESS));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount(USD, UINT64_C(5656854249492380), -13),
|
||||
XRP(70.710678),
|
||||
IOUAmount(200000)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount(USD, UINT64_C(5656854249492380), -13),
|
||||
XRP(70.710678),
|
||||
IOUAmount(200000)));
|
||||
else
|
||||
BEAST_EXPECT(amm.expectBalances(
|
||||
STAmount(USD, UINT64_C(565'685424949238), -12),
|
||||
XRP(70.710679),
|
||||
IOUAmount(200000)));
|
||||
BEAST_EXPECT(amm.expectLPTokens(alice, IOUAmount(0)));
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(29.289322)));
|
||||
if (!features[fixAMMv1_3])
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(29.289322)));
|
||||
else
|
||||
BEAST_EXPECT(expectLedgerEntryRoot(
|
||||
env, alice, aliceXrpBalance + XRP(29.289321)));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1780,13 +2040,18 @@ class AMMClawback_test : public jtx::AMMTest
|
||||
testFeatureDisabled(all - featureAMMClawback);
|
||||
testAMMClawbackSpecificAmount(all);
|
||||
testAMMClawbackExceedBalance(all);
|
||||
testAMMClawbackExceedBalance(all - fixAMMv1_3);
|
||||
testAMMClawbackAll(all);
|
||||
testAMMClawbackAll(all - fixAMMv1_3);
|
||||
testAMMClawbackSameIssuerAssets(all);
|
||||
testAMMClawbackSameIssuerAssets(all - fixAMMv1_3);
|
||||
testAMMClawbackSameCurrency(all);
|
||||
testAMMClawbackIssuesEachOther(all);
|
||||
testNotHoldingLptoken(all);
|
||||
testAssetFrozen(all);
|
||||
testAssetFrozen(all - fixAMMv1_3);
|
||||
testSingleDepositAndClawback(all);
|
||||
testSingleDepositAndClawback(all - fixAMMv1_3);
|
||||
}
|
||||
};
|
||||
BEAST_DEFINE_TESTSUITE(AMMClawback, app, ripple);
|
||||
|
||||
@@ -1451,7 +1451,7 @@ private:
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testRmFundedOffer(all);
|
||||
testRmFundedOffer(all - fixAMMv1_1);
|
||||
testRmFundedOffer(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testEnforceNoRipple(all);
|
||||
testFillModes(all);
|
||||
testOfferCrossWithXRP(all);
|
||||
@@ -1465,7 +1465,7 @@ private:
|
||||
testOfferCreateThenCross(all);
|
||||
testSellFlagExceedLimit(all);
|
||||
testGatewayCrossCurrency(all);
|
||||
testGatewayCrossCurrency(all - fixAMMv1_1);
|
||||
testGatewayCrossCurrency(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testBridgedCross(all);
|
||||
testSellWithFillOrKill(all);
|
||||
testTransferRateOffer(all);
|
||||
@@ -1473,7 +1473,7 @@ private:
|
||||
testBadPathAssert(all);
|
||||
testSellFlagBasic(all);
|
||||
testDirectToDirectPath(all);
|
||||
testDirectToDirectPath(all - fixAMMv1_1);
|
||||
testDirectToDirectPath(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testRequireAuth(all);
|
||||
testMissingAuth(all);
|
||||
}
|
||||
@@ -2156,6 +2156,7 @@ private:
|
||||
OfferCrossing::no,
|
||||
std::nullopt,
|
||||
smax,
|
||||
std::nullopt,
|
||||
flowJournal);
|
||||
}();
|
||||
|
||||
@@ -4062,9 +4063,9 @@ private:
|
||||
testBookStep(all);
|
||||
testBookStep(all | ownerPaysFee);
|
||||
testTransferRate(all | ownerPaysFee);
|
||||
testTransferRate((all - fixAMMv1_1) | ownerPaysFee);
|
||||
testTransferRate((all - fixAMMv1_1 - fixAMMv1_3) | ownerPaysFee);
|
||||
testTransferRateNoOwnerFee(all);
|
||||
testTransferRateNoOwnerFee(all - fixAMMv1_1);
|
||||
testTransferRateNoOwnerFee(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
testLimitQuality();
|
||||
testXRPPathLoop();
|
||||
}
|
||||
@@ -4075,7 +4076,7 @@ private:
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testStepLimit(all);
|
||||
testStepLimit(all - fixAMMv1_1);
|
||||
testStepLimit(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -4084,7 +4085,7 @@ private:
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
test_convert_all_of_an_asset(all);
|
||||
test_convert_all_of_an_asset(all - fixAMMv1_1);
|
||||
test_convert_all_of_an_asset(all - fixAMMv1_1 - fixAMMv1_3);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+950
-244
File diff suppressed because it is too large.
Load diff
@@ -335,26 +335,11 @@ public:
|
||||
env(check::cancel(becky, checkId));
|
||||
env.close();
|
||||
|
||||
// Lambda to create an escrow.
|
||||
auto escrowCreate = [](jtx::Account const& account,
|
||||
jtx::Account const& to,
|
||||
STAmount const& amount,
|
||||
NetClock::time_point const& cancelAfter) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[jss::Destination] = to.human();
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
jv[sfFinishAfter.jsonName] =
|
||||
cancelAfter.time_since_epoch().count() + 1;
|
||||
jv[sfCancelAfter.jsonName] =
|
||||
cancelAfter.time_since_epoch().count() + 2;
|
||||
return jv;
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
std::uint32_t const escrowSeq{env.seq(alice)};
|
||||
env(escrowCreate(alice, becky, XRP(333), env.now() + 2s));
|
||||
env(escrow::create(alice, becky, XRP(333)),
|
||||
escrow::finish_time(env.now() + 3s),
|
||||
escrow::cancel_time(env.now() + 4s));
|
||||
env.close();
|
||||
|
||||
// alice and becky should be unable to delete their accounts because
|
||||
@@ -366,17 +351,39 @@ public:
|
||||
// Now cancel the escrow, but create a payment channel between
|
||||
// alice and becky.
|
||||
|
||||
// Lambda to cancel an escrow.
|
||||
auto escrowCancel =
|
||||
[](Account const& account, Account const& from, std::uint32_t seq) {
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Account] = account.human();
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
};
|
||||
env(escrowCancel(becky, alice, escrowSeq));
|
||||
bool const withTokenEscrow =
|
||||
env.current()->rules().enabled(featureTokenEscrow);
|
||||
if (withTokenEscrow)
|
||||
{
|
||||
Account const gw1("gw1");
|
||||
Account const carol("carol");
|
||||
auto const USD = gw1["USD"];
|
||||
env.fund(XRP(100000), carol, gw1);
|
||||
env(fset(gw1, asfAllowTrustLineLocking));
|
||||
env.close();
|
||||
env.trust(USD(10000), carol);
|
||||
env.close();
|
||||
env(pay(gw1, carol, USD(100)));
|
||||
env.close();
|
||||
|
||||
std::uint32_t const escrowSeq{env.seq(carol)};
|
||||
env(escrow::create(carol, becky, USD(1)),
|
||||
escrow::finish_time(env.now() + 3s),
|
||||
escrow::cancel_time(env.now() + 4s));
|
||||
env.close();
|
||||
|
||||
incLgrSeqForAccDel(env, gw1);
|
||||
|
||||
env(acctdelete(gw1, becky),
|
||||
fee(acctDelFee),
|
||||
ter(tecHAS_OBLIGATIONS));
|
||||
env.close();
|
||||
|
||||
env(escrow::cancel(becky, carol, escrowSeq));
|
||||
env.close();
|
||||
}
|
||||
|
||||
env(escrow::cancel(becky, alice, escrowSeq));
|
||||
env.close();
|
||||
|
||||
Keylet const alicePayChanKey{
|
||||
|
||||
@@ -3765,6 +3765,8 @@ class Batch_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
// delegated non atomic inner (AccountSet)
|
||||
// this also makes sure tfInnerBatchTxn won't block delegated AccountSet
|
||||
// with granular permission
|
||||
{
|
||||
test::jtx::Env env{*this, envconfig()};
|
||||
|
||||
@@ -3810,6 +3812,145 @@ class Batch_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(env.balance(alice) == preAlice - XRP(2) - batchFee);
|
||||
BEAST_EXPECT(env.balance(bob) == preBob + XRP(2));
|
||||
}
|
||||
|
||||
// delegated non atomic inner (MPTokenIssuanceSet)
|
||||
// this also makes sure tfInnerBatchTxn won't block delegated
|
||||
// MPTokenIssuanceSet with granular permission
|
||||
{
|
||||
test::jtx::Env env{*this, envconfig()};
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
auto const mptID = makeMptID(env.seq(alice), alice);
|
||||
MPTTester mpt(env, alice, {.fund = false});
|
||||
env.close();
|
||||
mpt.create({.flags = tfMPTCanLock});
|
||||
env.close();
|
||||
|
||||
// alice gives granular permission to bob of MPTokenIssuanceLock
|
||||
env(delegate::set(
|
||||
alice, bob, {"MPTokenIssuanceLock", "MPTokenIssuanceUnlock"}));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
|
||||
Json::Value jv1;
|
||||
jv1[sfTransactionType] = jss::MPTokenIssuanceSet;
|
||||
jv1[sfAccount] = alice.human();
|
||||
jv1[sfDelegate] = bob.human();
|
||||
jv1[sfSequence] = seq + 1;
|
||||
jv1[sfMPTokenIssuanceID] = to_string(mptID);
|
||||
jv1[sfFlags] = tfMPTLock;
|
||||
|
||||
Json::Value jv2;
|
||||
jv2[sfTransactionType] = jss::MPTokenIssuanceSet;
|
||||
jv2[sfAccount] = alice.human();
|
||||
jv2[sfDelegate] = bob.human();
|
||||
jv2[sfSequence] = seq + 2;
|
||||
jv2[sfMPTokenIssuanceID] = to_string(mptID);
|
||||
jv2[sfFlags] = tfMPTUnlock;
|
||||
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(alice, seq, batchFee, tfAllOrNothing),
|
||||
batch::inner(jv1, seq + 1),
|
||||
batch::inner(jv2, seq + 2));
|
||||
env.close();
|
||||
|
||||
std::vector<TestLedgerData> testCases = {
|
||||
{0, "Batch", "tesSUCCESS", batchID, std::nullopt},
|
||||
{1, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[0], batchID},
|
||||
{2, "MPTokenIssuanceSet", "tesSUCCESS", txIDs[1], batchID},
|
||||
};
|
||||
validateClosedLedger(env, testCases);
|
||||
}
|
||||
|
||||
// delegated non atomic inner (TrustSet)
|
||||
// this also makes sure tfInnerBatchTxn won't block delegated TrustSet
|
||||
// with granular permission
|
||||
{
|
||||
test::jtx::Env env{*this, envconfig()};
|
||||
Account gw{"gw"};
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
|
||||
env(delegate::set(
|
||||
gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(gw);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
|
||||
auto jv1 = trust(gw, gw["USD"](0), alice, tfSetfAuth);
|
||||
jv1[sfDelegate] = bob.human();
|
||||
auto jv2 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
|
||||
jv2[sfDelegate] = bob.human();
|
||||
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(gw, seq, batchFee, tfAllOrNothing),
|
||||
batch::inner(jv1, seq + 1),
|
||||
batch::inner(jv2, seq + 2));
|
||||
env.close();
|
||||
|
||||
std::vector<TestLedgerData> testCases = {
|
||||
{0, "Batch", "tesSUCCESS", batchID, std::nullopt},
|
||||
{1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
|
||||
{2, "TrustSet", "tesSUCCESS", txIDs[1], batchID},
|
||||
};
|
||||
validateClosedLedger(env, testCases);
|
||||
}
|
||||
|
||||
// inner transaction not authorized by the delegating account.
|
||||
{
|
||||
test::jtx::Env env{*this, envconfig()};
|
||||
Account gw{"gw"};
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
|
||||
env(delegate::set(
|
||||
gw, bob, {"TrustlineAuthorize", "TrustlineFreeze"}));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(gw);
|
||||
auto const batchFee = batch::calcBatchFee(env, 0, 2);
|
||||
|
||||
auto jv1 = trust(gw, gw["USD"](0), alice, tfSetFreeze);
|
||||
jv1[sfDelegate] = bob.human();
|
||||
auto jv2 = trust(gw, gw["USD"](0), alice, tfClearFreeze);
|
||||
jv2[sfDelegate] = bob.human();
|
||||
|
||||
auto const [txIDs, batchID] = submitBatch(
|
||||
env,
|
||||
tesSUCCESS,
|
||||
batch::outer(gw, seq, batchFee, tfIndependent),
|
||||
batch::inner(jv1, seq + 1),
|
||||
// tecNO_DELEGATE_PERMISSION: not authorized to clear freeze
|
||||
batch::inner(jv2, seq + 2));
|
||||
env.close();
|
||||
|
||||
std::vector<TestLedgerData> testCases = {
|
||||
{0, "Batch", "tesSUCCESS", batchID, std::nullopt},
|
||||
{1, "TrustSet", "tesSUCCESS", txIDs[0], batchID},
|
||||
{2, "TrustSet", "tecNO_DELEGATE_PERMISSION", txIDs[1], batchID},
|
||||
};
|
||||
validateClosedLedger(env, testCases);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -558,8 +558,11 @@ public:
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testAll(sa);
|
||||
testAll(sa - featureFlowSortStrands);
|
||||
testAll(sa - featureFlowCross - featureFlowSortStrands);
|
||||
testAll(sa - featurePermissionedDEX);
|
||||
testAll(sa - featureFlowSortStrands - featurePermissionedDEX);
|
||||
testAll(
|
||||
sa - featureFlowCross - featureFlowSortStrands -
|
||||
featurePermissionedDEX);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+138
-76
@@ -209,10 +209,10 @@ class Delegate_test : public beast::unit_test::suite
|
||||
}
|
||||
|
||||
// when authorizing account which does not exist, should return
|
||||
// terNO_ACCOUNT
|
||||
// tecNO_TARGET
|
||||
{
|
||||
env(delegate::set(gw, Account("unknown"), {"Payment"}),
|
||||
ter(terNO_ACCOUNT));
|
||||
ter(tecNO_TARGET));
|
||||
}
|
||||
|
||||
// non-delegatable transaction
|
||||
@@ -310,8 +310,9 @@ class Delegate_test : public beast::unit_test::suite
|
||||
|
||||
{
|
||||
// Fee should be checked before permission check,
|
||||
// otherwise tecNO_PERMISSION returned when permission check fails
|
||||
// could cause context reset to pay fee because it is tec error
|
||||
// otherwise tecNO_DELEGATE_PERMISSION returned when permission
|
||||
// check fails could cause context reset to pay fee because it is
|
||||
// tec error
|
||||
auto aliceBalance = env.balance(alice);
|
||||
auto bobBalance = env.balance(bob);
|
||||
auto carolBalance = env.balance(carol);
|
||||
@@ -526,12 +527,12 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// bob does not have permission to create check
|
||||
env(check::create(alice, bob, XRP(10)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// carol does not have permission to create check
|
||||
env(check::create(alice, bob, XRP(10)),
|
||||
delegate::as(carol),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
void
|
||||
@@ -566,7 +567,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// delegate ledger object is not created yet
|
||||
env(pay(gw, alice, USD(50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.require(balance(bob, bobBalance - drops(baseFee)));
|
||||
bobBalance = env.balance(bob, XRP);
|
||||
|
||||
@@ -579,7 +580,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// bob sends a payment transaction on behalf of gw
|
||||
env(pay(gw, alice, USD(50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
env.require(balance(bob, bobBalance - drops(baseFee)));
|
||||
bobBalance = env.balance(bob, XRP);
|
||||
@@ -596,7 +597,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// can not send XRP
|
||||
env(pay(gw, alice, XRP(50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
env.require(balance(bob, bobBalance - drops(baseFee)));
|
||||
bobBalance = env.balance(bob, XRP);
|
||||
@@ -684,7 +685,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// permission
|
||||
env(pay(gw, alice, USD(50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
env.require(balance(bob, bobBalance - drops(baseFee)));
|
||||
bobBalance = env.balance(bob, XRP);
|
||||
@@ -729,7 +730,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// has unfreeze permission
|
||||
env(trust(alice, gw["USD"](50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// alice creates trustline by herself
|
||||
@@ -743,38 +744,38 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// unsupported flags
|
||||
env(trust(alice, gw["USD"](50), tfSetNoRipple),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(trust(alice, gw["USD"](50), tfClearNoRipple),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetDeepFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(trust(gw, gw["USD"](0), alice, tfClearDeepFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// supported flags with wrong permission
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetfAuth),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
|
||||
env.close();
|
||||
env(trust(gw, gw["USD"](0), alice, tfClearFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
// although trustline authorize is granted, bob can not change the
|
||||
// limit number
|
||||
env(trust(gw, gw["USD"](50), alice, tfSetfAuth),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// supported flags with correct permission
|
||||
@@ -795,30 +796,30 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// permission
|
||||
env(trust(gw, gw["USD"](0), alice, tfSetFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// cannot update LimitAmount with granular permission, both high and
|
||||
// low account
|
||||
env(trust(alice, gw["USD"](100)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(trust(gw, alice["USD"](100)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// can not set QualityIn or QualityOut
|
||||
auto tx = trust(alice, gw["USD"](50));
|
||||
tx["QualityIn"] = "1000";
|
||||
env(tx, delegate::as(bob), ter(tecNO_PERMISSION));
|
||||
env(tx, delegate::as(bob), ter(tecNO_DELEGATE_PERMISSION));
|
||||
auto tx2 = trust(alice, gw["USD"](50));
|
||||
tx2["QualityOut"] = "1000";
|
||||
env(tx2, delegate::as(bob), ter(tecNO_PERMISSION));
|
||||
env(tx2, delegate::as(bob), ter(tecNO_DELEGATE_PERMISSION));
|
||||
auto tx3 = trust(gw, alice["USD"](50));
|
||||
tx3["QualityIn"] = "1000";
|
||||
env(tx3, delegate::as(bob), ter(tecNO_PERMISSION));
|
||||
env(tx3, delegate::as(bob), ter(tecNO_DELEGATE_PERMISSION));
|
||||
auto tx4 = trust(gw, alice["USD"](50));
|
||||
tx4["QualityOut"] = "1000";
|
||||
env(tx4, delegate::as(bob), ter(tecNO_PERMISSION));
|
||||
env(tx4, delegate::as(bob), ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// granting TrustSet can make it work
|
||||
env(delegate::set(gw, bob, {"TrustSet"}));
|
||||
@@ -828,7 +829,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env(tx5, delegate::as(bob));
|
||||
auto tx6 = trust(alice, gw["USD"](50));
|
||||
tx6["QualityOut"] = "1000";
|
||||
env(tx6, delegate::as(bob), ter(tecNO_PERMISSION));
|
||||
env(tx6, delegate::as(bob), ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(delegate::set(alice, bob, {"TrustSet"}));
|
||||
env.close();
|
||||
env(tx6, delegate::as(bob));
|
||||
@@ -847,14 +848,14 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// bob does not have permission
|
||||
env(trust(alice, gw["USD"](50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(delegate::set(
|
||||
alice, bob, {"TrustlineUnfreeze", "NFTokenCreateOffer"}));
|
||||
env.close();
|
||||
// bob still does not have permission
|
||||
env(trust(alice, gw["USD"](50)),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// add TrustSet permission and some unrelated permission
|
||||
env(delegate::set(
|
||||
@@ -893,6 +894,25 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env(trust(alice, gw["USD"](50), tfClearNoRipple),
|
||||
delegate::as(bob));
|
||||
}
|
||||
|
||||
// tfFullyCanonicalSig won't block delegated transaction
|
||||
{
|
||||
Env env(*this);
|
||||
Account gw{"gw"};
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(10000), gw, alice, bob);
|
||||
env(fset(gw, asfRequireAuth));
|
||||
env.close();
|
||||
env(trust(alice, gw["USD"](50)));
|
||||
env.close();
|
||||
|
||||
env(delegate::set(gw, bob, {"TrustlineAuthorize"}));
|
||||
env.close();
|
||||
env(trust(
|
||||
gw, gw["USD"](0), alice, tfSetfAuth | tfFullyCanonicalSig),
|
||||
delegate::as(bob));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -920,16 +940,15 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// on behalf of alice
|
||||
std::string const domain = "example.com";
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain.fieldName] = strHex(domain);
|
||||
jt[sfDelegate.fieldName] = bob.human();
|
||||
jt[sfFlags.fieldName] = tfFullyCanonicalSig;
|
||||
jt[sfDomain] = strHex(domain);
|
||||
jt[sfDelegate] = bob.human();
|
||||
|
||||
// add granular permission related to AccountSet but is not the
|
||||
// correct permission for domain set
|
||||
env(delegate::set(
|
||||
alice, bob, {"TrustlineUnfreeze", "AccountEmailHashSet"}));
|
||||
env.close();
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// alice give granular permission of AccountDomainSet to bob
|
||||
env(delegate::set(alice, bob, {"AccountDomainSet"}));
|
||||
@@ -940,25 +959,24 @@ class Delegate_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
|
||||
|
||||
// bob can reset domain
|
||||
jt[sfDomain.fieldName] = "";
|
||||
jt[sfDomain] = "";
|
||||
env(jt);
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfDomain));
|
||||
|
||||
// if flag is not equal to tfFullyCanonicalSig, which means bob
|
||||
// is trying to set the flag at the same time, it will fail
|
||||
// bob tries to set unauthorized flag, it will fail
|
||||
std::string const failDomain = "fail_domain_update";
|
||||
jt[sfFlags.fieldName] = tfRequireAuth;
|
||||
jt[sfDomain.fieldName] = strHex(failDomain);
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
jt[sfFlags] = tfRequireAuth;
|
||||
jt[sfDomain] = strHex(failDomain);
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
// reset flag number
|
||||
jt[sfFlags.fieldName] = tfFullyCanonicalSig;
|
||||
jt[sfFlags] = 0;
|
||||
|
||||
// bob tries to update domain and set email hash,
|
||||
// but he does not have permission to set email hash
|
||||
jt[sfDomain.fieldName] = strHex(domain);
|
||||
jt[sfDomain] = strHex(domain);
|
||||
std::string const mh("5F31A79367DC3137FADA860C05742EE6");
|
||||
jt[sfEmailHash.fieldName] = mh;
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
jt[sfEmailHash] = mh;
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// alice give granular permission of AccountEmailHashSet to bob
|
||||
env(delegate::set(
|
||||
@@ -970,8 +988,8 @@ class Delegate_test : public beast::unit_test::suite
|
||||
|
||||
// bob does not have permission to set message key for alice
|
||||
auto const rkp = randomKeyPair(KeyType::ed25519);
|
||||
jt[sfMessageKey.fieldName] = strHex(rkp.first.slice());
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
jt[sfMessageKey] = strHex(rkp.first.slice());
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// alice give granular permission of AccountMessageKeySet to bob
|
||||
env(delegate::set(
|
||||
@@ -987,12 +1005,14 @@ class Delegate_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(
|
||||
strHex((*env.le(alice))[sfMessageKey]) ==
|
||||
strHex(rkp.first.slice()));
|
||||
jt[sfMessageKey.fieldName] = "";
|
||||
jt[sfMessageKey] = "";
|
||||
env(jt);
|
||||
BEAST_EXPECT(!env.le(alice)->isFieldPresent(sfMessageKey));
|
||||
|
||||
// bob does not have permission to set transfer rate for alice
|
||||
env(rate(alice, 2.0), delegate::as(bob), ter(tecNO_PERMISSION));
|
||||
env(rate(alice, 2.0),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// alice give granular permission of AccountTransferRateSet to bob
|
||||
env(delegate::set(
|
||||
@@ -1004,14 +1024,13 @@ class Delegate_test : public beast::unit_test::suite
|
||||
"AccountTransferRateSet"}));
|
||||
env.close();
|
||||
auto jtRate = rate(alice, 2.0);
|
||||
jtRate[sfDelegate.fieldName] = bob.human();
|
||||
jtRate[sfFlags.fieldName] = tfFullyCanonicalSig;
|
||||
jtRate[sfDelegate] = bob.human();
|
||||
env(jtRate, delegate::as(bob));
|
||||
BEAST_EXPECT((*env.le(alice))[sfTransferRate] == 2000000000);
|
||||
|
||||
// bob does not have permission to set ticksize for alice
|
||||
jt[sfTickSize.fieldName] = 8;
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
jt[sfTickSize] = 8;
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// alice give granular permission of AccountTickSizeSet to bob
|
||||
env(delegate::set(
|
||||
@@ -1029,7 +1048,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// can not set asfRequireAuth flag for alice
|
||||
env(fset(alice, asfRequireAuth),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// reset Delegate will delete the Delegate
|
||||
// object
|
||||
@@ -1038,15 +1057,15 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// alice
|
||||
env(fset(alice, asfRequireAuth),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
// alice can set for herself
|
||||
env(fset(alice, asfRequireAuth));
|
||||
env.require(flags(alice, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
// can not update tick size because bob no longer has permission
|
||||
jt[sfTickSize.fieldName] = 7;
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
jt[sfTickSize] = 7;
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
env(delegate::set(
|
||||
alice,
|
||||
@@ -1060,12 +1079,11 @@ class Delegate_test : public beast::unit_test::suite
|
||||
std::string const locator =
|
||||
"9633EC8AF54F16B5286DB1D7B519EF49EEFC050C0C8AC4384F1D88ACD1BFDF"
|
||||
"05";
|
||||
auto jt2 = noop(alice);
|
||||
jt2[sfDomain.fieldName] = strHex(domain);
|
||||
jt2[sfDelegate.fieldName] = bob.human();
|
||||
jt2[sfWalletLocator.fieldName] = locator;
|
||||
jt2[sfFlags.fieldName] = tfFullyCanonicalSig;
|
||||
env(jt2, ter(tecNO_PERMISSION));
|
||||
auto jv2 = noop(alice);
|
||||
jv2[sfDomain] = strHex(domain);
|
||||
jv2[sfDelegate] = bob.human();
|
||||
jv2[sfWalletLocator] = locator;
|
||||
env(jv2, ter(tecNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// can not set AccountSet flags on behalf of other account
|
||||
@@ -1080,7 +1098,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// bob can not set flag on behalf of alice
|
||||
env(fset(alice, flag),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
// alice set by herself
|
||||
env(fset(alice, flag));
|
||||
env.close();
|
||||
@@ -1088,7 +1106,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// bob can not clear on behalf of alice
|
||||
env(fclear(alice, flag),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
};
|
||||
|
||||
// testSetClearFlag(asfNoFreeze);
|
||||
@@ -1117,19 +1135,19 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// bob can not set asfAccountTxnID on behalf of alice
|
||||
env(fset(alice, asfAccountTxnID),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(fset(alice, asfAccountTxnID));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(alice)->isFieldPresent(sfAccountTxnID));
|
||||
env(fclear(alice, asfAccountTxnID),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// bob can not set asfAuthorizedNFTokenMinter on behalf of alice
|
||||
Json::Value jt = fset(alice, asfAuthorizedNFTokenMinter);
|
||||
jt[sfDelegate.fieldName] = bob.human();
|
||||
jt[sfNFTokenMinter.fieldName] = bob.human();
|
||||
env(jt, ter(tecNO_PERMISSION));
|
||||
jt[sfDelegate] = bob.human();
|
||||
jt[sfNFTokenMinter] = bob.human();
|
||||
env(jt, ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// bob gives alice some permissions
|
||||
env(delegate::set(
|
||||
@@ -1145,14 +1163,14 @@ class Delegate_test : public beast::unit_test::suite
|
||||
// behalf of bob.
|
||||
env(fset(alice, asfNoFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
env(fset(bob, asfNoFreeze));
|
||||
env.close();
|
||||
env.require(flags(bob, asfNoFreeze));
|
||||
// alice can not clear on behalf of bob
|
||||
env(fclear(alice, asfNoFreeze),
|
||||
delegate::as(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
|
||||
// bob can not set asfDisableMaster on behalf of alice
|
||||
Account const bobKey{"bobKey", KeyType::secp256k1};
|
||||
@@ -1161,7 +1179,29 @@ class Delegate_test : public beast::unit_test::suite
|
||||
env(fset(alice, asfDisableMaster),
|
||||
delegate::as(bob),
|
||||
sig(bob),
|
||||
ter(tecNO_PERMISSION));
|
||||
ter(tecNO_DELEGATE_PERMISSION));
|
||||
}
|
||||
|
||||
// tfFullyCanonicalSig won't block delegated transaction
|
||||
{
|
||||
Env env(*this);
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(10000), alice, bob);
|
||||
env.close();
|
||||
|
||||
env(delegate::set(
|
||||
alice, bob, {"AccountDomainSet", "AccountEmailHashSet"}));
|
||||
env.close();
|
||||
|
||||
std::string const domain = "example.com";
|
||||
auto jt = noop(alice);
|
||||
jt[sfDomain] = strHex(domain);
|
||||
jt[sfDelegate] = bob.human();
|
||||
jt[sfFlags] = tfFullyCanonicalSig;
|
||||
|
||||
env(jt);
|
||||
BEAST_EXPECT((*env.le(alice))[sfDomain] == makeSlice(domain));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1189,7 +1229,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
{.account = alice,
|
||||
.flags = tfMPTLock,
|
||||
.delegate = bob,
|
||||
.err = tecNO_PERMISSION});
|
||||
.err = tecNO_DELEGATE_PERMISSION});
|
||||
|
||||
// alice gives granular permission to bob of MPTokenIssuanceUnlock
|
||||
env(delegate::set(alice, bob, {"MPTokenIssuanceUnlock"}));
|
||||
@@ -1199,7 +1239,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
{.account = alice,
|
||||
.flags = tfMPTLock,
|
||||
.delegate = bob,
|
||||
.err = tecNO_PERMISSION});
|
||||
.err = tecNO_DELEGATE_PERMISSION});
|
||||
// bob now has lock permission, but does not have unlock permission
|
||||
env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
|
||||
env.close();
|
||||
@@ -1208,7 +1248,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
{.account = alice,
|
||||
.flags = tfMPTUnlock,
|
||||
.delegate = bob,
|
||||
.err = tecNO_PERMISSION});
|
||||
.err = tecNO_DELEGATE_PERMISSION});
|
||||
|
||||
// now bob can lock and unlock
|
||||
env(delegate::set(
|
||||
@@ -1241,7 +1281,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
{.account = alice,
|
||||
.flags = tfMPTUnlock,
|
||||
.delegate = bob,
|
||||
.err = tecNO_PERMISSION});
|
||||
.err = tecNO_DELEGATE_PERMISSION});
|
||||
|
||||
// alice gives bob some unrelated permission with
|
||||
// MPTokenIssuanceLock
|
||||
@@ -1255,7 +1295,7 @@ class Delegate_test : public beast::unit_test::suite
|
||||
{.account = alice,
|
||||
.flags = tfMPTUnlock,
|
||||
.delegate = bob,
|
||||
.err = tecNO_PERMISSION});
|
||||
.err = tecNO_DELEGATE_PERMISSION});
|
||||
|
||||
// alice add MPTokenIssuanceSet to permissions
|
||||
env(delegate::set(
|
||||
@@ -1271,6 +1311,28 @@ class Delegate_test : public beast::unit_test::suite
|
||||
mpt.set({.account = alice, .flags = tfMPTUnlock, .delegate = bob});
|
||||
mpt.set({.account = alice, .flags = tfMPTLock, .delegate = bob});
|
||||
}
|
||||
|
||||
// tfFullyCanonicalSig won't block delegated transaction
|
||||
{
|
||||
Env env(*this);
|
||||
Account alice{"alice"};
|
||||
Account bob{"bob"};
|
||||
env.fund(XRP(100000), alice, bob);
|
||||
env.close();
|
||||
|
||||
MPTTester mpt(env, alice, {.fund = false});
|
||||
env.close();
|
||||
mpt.create({.flags = tfMPTCanLock});
|
||||
env.close();
|
||||
|
||||
// alice gives granular permission to bob of MPTokenIssuanceLock
|
||||
env(delegate::set(alice, bob, {"MPTokenIssuanceLock"}));
|
||||
env.close();
|
||||
mpt.set(
|
||||
{.account = alice,
|
||||
.flags = tfMPTLock | tfFullyCanonicalSig,
|
||||
.delegate = bob});
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -143,7 +143,9 @@ public:
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
test_convert_all_of_an_asset(sa - featureFlowCross);
|
||||
test_convert_all_of_an_asset(
|
||||
sa - featureFlowCross - featurePermissionedDEX);
|
||||
test_convert_all_of_an_asset(sa - featurePermissionedDEX);
|
||||
test_convert_all_of_an_asset(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -714,12 +714,12 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
if (!supportsPreauth)
|
||||
{
|
||||
auto const seq1 = env.seq(alice);
|
||||
env(escrow(alice, becky, XRP(100)),
|
||||
finish_time(env.now() + 1s));
|
||||
env(escrow::create(alice, becky, XRP(100)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// Failed as rule is disabled
|
||||
env(finish(gw, alice, seq1),
|
||||
env(escrow::finish(gw, alice, seq1),
|
||||
fee(1500),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
@@ -1387,12 +1387,13 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow(alice, bob, XRP(1000)), finish_time(env.now() + 1s));
|
||||
env(escrow::create(alice, bob, XRP(1000)),
|
||||
escrow::finish_time(env.now() + 1s));
|
||||
env.close();
|
||||
|
||||
// zelda can't finish escrow with invalid credentials
|
||||
{
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({}),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
@@ -1404,14 +1405,14 @@ struct DepositPreauth_test : public beast::unit_test::suite
|
||||
"0E0B04ED60588A758B67E21FBBE95AC5A63598BA951761DC0EC9C08D7E"
|
||||
"01E034";
|
||||
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({invalidIdx}),
|
||||
ter(tecBAD_CREDENTIALS));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{ // Ledger closed, time increased, zelda can't finish escrow
|
||||
env(finish(zelda, alice, seq),
|
||||
env(escrow::finish(zelda, alice, seq),
|
||||
credentials::ids({credIdx}),
|
||||
fee(1500),
|
||||
ter(tecEXPIRED));
|
||||
|
||||
@@ -147,7 +147,8 @@ public:
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testXRPDiscrepancy(sa - featureFlowCross);
|
||||
testXRPDiscrepancy(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testXRPDiscrepancy(sa - featurePermissionedDEX);
|
||||
testXRPDiscrepancy(sa);
|
||||
}
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+495
-1084
File diff suppressed because it is too large.
Load diff
@@ -37,62 +37,36 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(
|
||||
setup.extension_compute_limit ==
|
||||
defaultSetup.extension_compute_limit);
|
||||
BEAST_EXPECT(
|
||||
setup.extension_size_limit ==
|
||||
defaultSetup.extension_size_limit);
|
||||
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = 50",
|
||||
"account_reserve = 1234567",
|
||||
"owner_reserve = 1234",
|
||||
"extension_compute_limit = 100",
|
||||
"extension_size_limit = 200",
|
||||
" gas_price = 300"});
|
||||
"owner_reserve = 1234"});
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == 50);
|
||||
BEAST_EXPECT(setup.account_reserve == 1234567);
|
||||
BEAST_EXPECT(setup.owner_reserve == 1234);
|
||||
BEAST_EXPECT(setup.extension_compute_limit == 100);
|
||||
BEAST_EXPECT(setup.extension_size_limit == 200);
|
||||
BEAST_EXPECT(setup.gas_price == 300);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = blah",
|
||||
"account_reserve = yada",
|
||||
"owner_reserve = foo",
|
||||
"extension_compute_limit = bar",
|
||||
"extension_size_limit = baz",
|
||||
"gas_price = qux"});
|
||||
"owner_reserve = foo"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(
|
||||
setup.extension_compute_limit ==
|
||||
defaultSetup.extension_compute_limit);
|
||||
BEAST_EXPECT(
|
||||
setup.extension_size_limit ==
|
||||
defaultSetup.extension_size_limit);
|
||||
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
|
||||
}
|
||||
{
|
||||
Section config;
|
||||
config.append(
|
||||
{"reference_fee = -50",
|
||||
"account_reserve = -1234567",
|
||||
"owner_reserve = -1234",
|
||||
"extension_compute_limit = -100",
|
||||
"extension_size_limit = -200",
|
||||
"gas_price = -300"});
|
||||
"owner_reserve = -1234"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
@@ -100,12 +74,6 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
setup.account_reserve == static_cast<std::uint32_t>(-1234567));
|
||||
BEAST_EXPECT(
|
||||
setup.owner_reserve == static_cast<std::uint32_t>(-1234));
|
||||
BEAST_EXPECT(
|
||||
setup.extension_compute_limit ==
|
||||
static_cast<std::uint32_t>(-100));
|
||||
BEAST_EXPECT(
|
||||
setup.extension_size_limit == static_cast<std::uint32_t>(-200));
|
||||
BEAST_EXPECT(setup.gas_price == static_cast<std::uint32_t>(-300));
|
||||
}
|
||||
{
|
||||
auto const big64 = std::to_string(
|
||||
@@ -116,22 +84,12 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64,
|
||||
"extension_compute_limit = " + big64,
|
||||
"extension_size_limit = " + big64,
|
||||
"gas_price = " + big64});
|
||||
"owner_reserve = " + big64});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
BEAST_EXPECT(setup.account_reserve == defaultSetup.account_reserve);
|
||||
BEAST_EXPECT(setup.owner_reserve == defaultSetup.owner_reserve);
|
||||
BEAST_EXPECT(
|
||||
setup.extension_compute_limit ==
|
||||
defaultSetup.extension_compute_limit);
|
||||
BEAST_EXPECT(
|
||||
setup.extension_size_limit ==
|
||||
defaultSetup.extension_size_limit);
|
||||
BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -494,6 +494,7 @@ struct Flow_test : public beast::unit_test::suite
|
||||
OfferCrossing::no,
|
||||
std::nullopt,
|
||||
smax,
|
||||
std::nullopt,
|
||||
flowJournal);
|
||||
}();
|
||||
|
||||
@@ -1475,7 +1476,8 @@ struct Flow_test : public beast::unit_test::suite
|
||||
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeats(sa - featureFlowCross);
|
||||
testWithFeats(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testWithFeats(sa - featurePermissionedDEX);
|
||||
testWithFeats(sa);
|
||||
testEmptyStrand(sa);
|
||||
}
|
||||
@@ -1490,13 +1492,16 @@ struct Flow_manual_test : public Flow_test
|
||||
auto const all = supported_amendments();
|
||||
FeatureBitset const flowCross{featureFlowCross};
|
||||
FeatureBitset const f1513{fix1513};
|
||||
FeatureBitset const permDex{featurePermissionedDEX};
|
||||
|
||||
testWithFeats(all - flowCross - f1513);
|
||||
testWithFeats(all - flowCross);
|
||||
testWithFeats(all - f1513);
|
||||
testWithFeats(all - flowCross - f1513 - permDex);
|
||||
testWithFeats(all - flowCross - permDex);
|
||||
testWithFeats(all - f1513 - permDex);
|
||||
testWithFeats(all - permDex);
|
||||
testWithFeats(all);
|
||||
|
||||
testEmptyStrand(all - f1513);
|
||||
testEmptyStrand(all - f1513 - permDex);
|
||||
testEmptyStrand(all - permDex);
|
||||
testEmptyStrand(all);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1885,6 +1885,31 @@ class Freeze_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Testing A1 nft buy offer when A2 deep frozen by issuer
|
||||
if (features[featureDeepFreeze] &&
|
||||
features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
env(trust(G1, A2["USD"](0), tfSetFreeze | tfSetDeepFreeze));
|
||||
env.close();
|
||||
|
||||
uint256 const nftID{token::getNextID(env, A2, 0u, tfTransferable)};
|
||||
env(token::mint(A2, 0), txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(10)), token::owner(A2));
|
||||
env.close();
|
||||
|
||||
env(token::acceptBuyOffer(A2, buyIdx), ter(tecFROZEN));
|
||||
env.close();
|
||||
|
||||
env(trust(G1, A2["USD"](0), tfClearFreeze | tfClearDeepFreeze));
|
||||
env.close();
|
||||
|
||||
env(token::acceptBuyOffer(A2, buyIdx));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Testing A2 nft offer sell when A2 frozen by currency holder
|
||||
{
|
||||
auto const sellOfferIndex = createNFTSellOffer(env, A2, USD(10));
|
||||
@@ -1944,6 +1969,68 @@ class Freeze_test : public beast::unit_test::suite
|
||||
env(trust(A2, limit, tfClearFreeze | tfClearDeepFreeze));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Testing brokered offer acceptance
|
||||
if (features[featureDeepFreeze] &&
|
||||
features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
Account broker{"broker"};
|
||||
env.fund(XRP(10000), broker);
|
||||
env.close();
|
||||
env(trust(G1, broker["USD"](1000), tfSetFreeze | tfSetDeepFreeze));
|
||||
env.close();
|
||||
|
||||
uint256 const nftID{token::getNextID(env, A2, 0u, tfTransferable)};
|
||||
env(token::mint(A2, 0), txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
uint256 const sellIdx = keylet::nftoffer(A2, env.seq(A2)).key;
|
||||
env(token::createOffer(A2, nftID, USD(10)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(11)), token::owner(A2));
|
||||
env.close();
|
||||
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)),
|
||||
ter(tecFROZEN));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Testing transfer fee
|
||||
if (features[featureDeepFreeze] &&
|
||||
features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
Account minter{"minter"};
|
||||
env.fund(XRP(10000), minter);
|
||||
env.close();
|
||||
env(trust(G1, minter["USD"](1000)));
|
||||
env.close();
|
||||
|
||||
uint256 const nftID{
|
||||
token::getNextID(env, minter, 0u, tfTransferable, 1u)};
|
||||
env(token::mint(minter, 0),
|
||||
token::xferFee(1u),
|
||||
txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
uint256 const minterSellIdx =
|
||||
keylet::nftoffer(minter, env.seq(minter)).key;
|
||||
env(token::createOffer(minter, nftID, drops(1)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(A2, minterSellIdx));
|
||||
env.close();
|
||||
|
||||
uint256 const sellIdx = keylet::nftoffer(A2, env.seq(A2)).key;
|
||||
env(token::createOffer(A2, nftID, USD(100)),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
env(trust(G1, minter["USD"](1000), tfSetFreeze | tfSetDeepFreeze));
|
||||
env.close();
|
||||
env(token::acceptSellOffer(A1, sellIdx), ter(tecFROZEN));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to extract trustline flags from open ledger
|
||||
@@ -2020,9 +2107,17 @@ public:
|
||||
};
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testAll(sa - featureFlowCross - featureDeepFreeze);
|
||||
testAll(sa - featureFlowCross);
|
||||
testAll(sa - featureDeepFreeze);
|
||||
testAll(
|
||||
sa - featureFlowCross - featureDeepFreeze - featurePermissionedDEX -
|
||||
fixEnforceNFTokenTrustlineV2);
|
||||
testAll(
|
||||
sa - featureFlowCross - featurePermissionedDEX -
|
||||
fixEnforceNFTokenTrustlineV2);
|
||||
testAll(
|
||||
sa - featureDeepFreeze - featurePermissionedDEX -
|
||||
fixEnforceNFTokenTrustlineV2);
|
||||
testAll(sa - featurePermissionedDEX - fixEnforceNFTokenTrustlineV2);
|
||||
testAll(sa - fixEnforceNFTokenTrustlineV2);
|
||||
testAll(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1694,15 +1694,6 @@ class MPToken_test : public beast::unit_test::suite
|
||||
jv[jss::SendMax] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, jss::SendMax.c_str());
|
||||
}
|
||||
// EscrowCreate
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = alice.human();
|
||||
jv[jss::Destination] = carol.human();
|
||||
jv[jss::Amount] = mpt.getJson(JsonOptions::none);
|
||||
test(jv, jss::Amount.c_str());
|
||||
}
|
||||
// OfferCreate
|
||||
{
|
||||
Json::Value jv = offer(alice, USD(100), mpt);
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class NFTokenAuth_test : public beast::unit_test::suite
|
||||
{
|
||||
auto
|
||||
mintAndOfferNFT(
|
||||
test::jtx::Env& env,
|
||||
test::jtx::Account const& account,
|
||||
test::jtx::PrettyAmount const& currency,
|
||||
uint32_t xfee = 0u)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
auto const nftID{
|
||||
token::getNextID(env, account, 0u, tfTransferable, xfee)};
|
||||
env(token::mint(account, 0),
|
||||
token::xferFee(xfee),
|
||||
txflags(tfTransferable));
|
||||
env.close();
|
||||
|
||||
auto const sellIdx = keylet::nftoffer(account, env.seq(account)).key;
|
||||
env(token::createOffer(account, nftID, currency),
|
||||
txflags(tfSellNFToken));
|
||||
env.close();
|
||||
|
||||
return std::make_tuple(nftID, sellIdx);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
testBuyOffer_UnauthorizedSeller(FeatureBitset features)
|
||||
{
|
||||
testcase("Unauthorized seller tries to accept buy offer");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, limit, A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(1000)));
|
||||
|
||||
auto const [nftID, _] = mintAndOfferNFT(env, A2, drops(1));
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
|
||||
// It should be possible to create a buy offer even if NFT owner is not
|
||||
// authorized
|
||||
env(token::createOffer(A1, nftID, USD(10)), token::owner(A2));
|
||||
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: G1 requires authorization of A2, no trust line exists
|
||||
env(token::acceptBuyOffer(A2, buyIdx), ter(tecNO_LINE));
|
||||
env.close();
|
||||
|
||||
// trust line created, but not authorized
|
||||
env(trust(A2, limit));
|
||||
|
||||
// test: G1 requires authorization of A2
|
||||
env(token::acceptBuyOffer(A2, buyIdx), ter(tecNO_AUTH));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old behavior: it is possible to sell tokens and receive IOUs
|
||||
// without the authorization
|
||||
env(token::acceptBuyOffer(A2, buyIdx));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(A2, USD) == USD(10));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateBuyOffer_UnauthorizedBuyer(FeatureBitset features)
|
||||
{
|
||||
testcase("Unauthorized buyer tries to create buy offer");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const [nftID, _] = mintAndOfferNFT(env, A2, drops(1));
|
||||
|
||||
// test: check that buyer can't make an offer if they're not authorized.
|
||||
env(token::createOffer(A1, nftID, USD(10)),
|
||||
token::owner(A2),
|
||||
ter(tecUNFUNDED_OFFER));
|
||||
env.close();
|
||||
|
||||
// Artificially create an unauthorized trustline with balance. Don't
|
||||
// close ledger before running the actual tests against this trustline.
|
||||
// After ledger is closed, the trustline will not exist.
|
||||
auto const unauthTrustline = [&](OpenView& view,
|
||||
beast::Journal) -> bool {
|
||||
auto const sleA1 =
|
||||
std::make_shared<SLE>(keylet::line(A1, G1, G1["USD"].currency));
|
||||
sleA1->setFieldAmount(sfBalance, A1["USD"](-1000));
|
||||
view.rawInsert(sleA1);
|
||||
return true;
|
||||
};
|
||||
env.app().openLedger().modify(unauthTrustline);
|
||||
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: check that buyer can't make an offer even with balance
|
||||
env(token::createOffer(A1, nftID, USD(10)),
|
||||
token::owner(A2),
|
||||
ter(tecNO_AUTH));
|
||||
}
|
||||
else
|
||||
{
|
||||
// old behavior: can create an offer if balance allows, regardless
|
||||
// ot authorization
|
||||
env(token::createOffer(A1, nftID, USD(10)), token::owner(A2));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAcceptBuyOffer_UnauthorizedBuyer(FeatureBitset features)
|
||||
{
|
||||
testcase("Seller tries to accept buy offer from unauth buyer");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
auto const [nftID, _] = mintAndOfferNFT(env, A2, drops(1));
|
||||
|
||||
// First we authorize buyer and seller so that he can create buy offer
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, limit, A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(10)));
|
||||
env(trust(A2, limit));
|
||||
env(trust(G1, limit, A2, tfSetfAuth));
|
||||
env(pay(G1, A2, USD(10)));
|
||||
env.close();
|
||||
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(10)), token::owner(A2));
|
||||
env.close();
|
||||
|
||||
env(pay(A1, G1, USD(10)));
|
||||
env(trust(A1, USD(0)));
|
||||
env(trust(G1, A1["USD"](0)));
|
||||
env.close();
|
||||
|
||||
// Replace an existing authorized trustline with artificial unauthorized
|
||||
// trustline with balance. Don't close ledger before running the actual
|
||||
// tests against this trustline. After ledger is closed, the trustline
|
||||
// will not exist.
|
||||
auto const unauthTrustline = [&](OpenView& view,
|
||||
beast::Journal) -> bool {
|
||||
auto const sleA1 =
|
||||
std::make_shared<SLE>(keylet::line(A1, G1, G1["USD"].currency));
|
||||
sleA1->setFieldAmount(sfBalance, A1["USD"](-1000));
|
||||
view.rawInsert(sleA1);
|
||||
return true;
|
||||
};
|
||||
env.app().openLedger().modify(unauthTrustline);
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: check that offer can't be accepted even with balance
|
||||
env(token::acceptBuyOffer(A2, buyIdx), ter(tecNO_AUTH));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSellOffer_UnauthorizedSeller(FeatureBitset features)
|
||||
{
|
||||
testcase(
|
||||
"Authorized buyer tries to accept sell offer from unauthorized "
|
||||
"seller");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, limit, A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(1000)));
|
||||
|
||||
auto const [nftID, _] = mintAndOfferNFT(env, A2, drops(1));
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: can't create sell offer if there is no trustline but auth
|
||||
// required
|
||||
env(token::createOffer(A2, nftID, USD(10)),
|
||||
txflags(tfSellNFToken),
|
||||
ter(tecNO_LINE));
|
||||
|
||||
env(trust(A2, limit));
|
||||
// test: can't create sell offer if not authorized to hold token
|
||||
env(token::createOffer(A2, nftID, USD(10)),
|
||||
txflags(tfSellNFToken),
|
||||
ter(tecNO_AUTH));
|
||||
|
||||
// Authorizing trustline to make an offer creation possible
|
||||
env(trust(G1, USD(0), A2, tfSetfAuth));
|
||||
env.close();
|
||||
auto const sellIdx = keylet::nftoffer(A2, env.seq(A2)).key;
|
||||
env(token::createOffer(A2, nftID, USD(10)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
//
|
||||
|
||||
// Reseting trustline to delete it. This allows to check if
|
||||
// already existing offers handled correctly
|
||||
env(trust(A2, USD(0)));
|
||||
env.close();
|
||||
|
||||
// test: G1 requires authorization of A1, no trust line exists
|
||||
env(token::acceptSellOffer(A1, sellIdx), ter(tecNO_LINE));
|
||||
env.close();
|
||||
|
||||
// trust line created, but not authorized
|
||||
env(trust(A2, limit));
|
||||
env.close();
|
||||
|
||||
// test: G1 requires authorization of A1
|
||||
env(token::acceptSellOffer(A1, sellIdx), ter(tecNO_AUTH));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const sellIdx = keylet::nftoffer(A2, env.seq(A2)).key;
|
||||
|
||||
// Old behavior: sell offer can be created without authorization
|
||||
env(token::createOffer(A2, nftID, USD(10)), txflags(tfSellNFToken));
|
||||
env.close();
|
||||
|
||||
// Old behavior: it is possible to sell NFT and receive IOUs
|
||||
// without the authorization
|
||||
env(token::acceptSellOffer(A1, sellIdx));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(A2, USD) == USD(10));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testSellOffer_UnauthorizedBuyer(FeatureBitset features)
|
||||
{
|
||||
testcase("Unauthorized buyer tries to accept sell offer");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A2, limit));
|
||||
env(trust(G1, limit, A2, tfSetfAuth));
|
||||
|
||||
auto const [_, sellIdx] = mintAndOfferNFT(env, A2, USD(10));
|
||||
|
||||
// test: check that buyer can't accept an offer if they're not
|
||||
// authorized.
|
||||
env(token::acceptSellOffer(A1, sellIdx), ter(tecINSUFFICIENT_FUNDS));
|
||||
env.close();
|
||||
|
||||
// Creating an artificial unauth trustline
|
||||
auto const unauthTrustline = [&](OpenView& view,
|
||||
beast::Journal) -> bool {
|
||||
auto const sleA1 =
|
||||
std::make_shared<SLE>(keylet::line(A1, G1, G1["USD"].currency));
|
||||
sleA1->setFieldAmount(sfBalance, A1["USD"](-1000));
|
||||
view.rawInsert(sleA1);
|
||||
return true;
|
||||
};
|
||||
env.app().openLedger().modify(unauthTrustline);
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
env(token::acceptSellOffer(A1, sellIdx), ter(tecNO_AUTH));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testBrokeredAcceptOffer_UnauthorizedBroker(FeatureBitset features)
|
||||
{
|
||||
testcase("Unauthorized broker bridges authorized buyer and seller.");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
Account broker{"broker"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2, broker);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, limit, A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(1000)));
|
||||
env(trust(A2, limit));
|
||||
env(trust(G1, limit, A2, tfSetfAuth));
|
||||
env(pay(G1, A2, USD(1000)));
|
||||
env.close();
|
||||
|
||||
auto const [nftID, sellIdx] = mintAndOfferNFT(env, A2, USD(10));
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(11)), token::owner(A2));
|
||||
env.close();
|
||||
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: G1 requires authorization of broker, no trust line exists
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)),
|
||||
ter(tecNO_LINE));
|
||||
env.close();
|
||||
|
||||
// trust line created, but not authorized
|
||||
env(trust(broker, limit));
|
||||
env.close();
|
||||
|
||||
// test: G1 requires authorization of broker
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)),
|
||||
ter(tecNO_AUTH));
|
||||
env.close();
|
||||
|
||||
// test: can still be brokered without broker fee.
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old behavior: broker can receive IOUs without the authorization
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(broker, USD) == USD(1));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testBrokeredAcceptOffer_UnauthorizedBuyer(FeatureBitset features)
|
||||
{
|
||||
testcase(
|
||||
"Authorized broker tries to bridge offers from unauthorized "
|
||||
"buyer.");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
Account broker{"broker"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2, broker);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, USD(0), A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(1000)));
|
||||
env(trust(A2, limit));
|
||||
env(trust(G1, USD(0), A2, tfSetfAuth));
|
||||
env(pay(G1, A2, USD(1000)));
|
||||
env(trust(broker, limit));
|
||||
env(trust(G1, USD(0), broker, tfSetfAuth));
|
||||
env(pay(G1, broker, USD(1000)));
|
||||
env.close();
|
||||
|
||||
auto const [nftID, sellIdx] = mintAndOfferNFT(env, A2, USD(10));
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(11)), token::owner(A2));
|
||||
env.close();
|
||||
|
||||
// Resetting buyer's trust line to delete it
|
||||
env(pay(A1, G1, USD(1000)));
|
||||
env(trust(A1, USD(0)));
|
||||
env.close();
|
||||
|
||||
auto const unauthTrustline = [&](OpenView& view,
|
||||
beast::Journal) -> bool {
|
||||
auto const sleA1 =
|
||||
std::make_shared<SLE>(keylet::line(A1, G1, G1["USD"].currency));
|
||||
sleA1->setFieldAmount(sfBalance, A1["USD"](-1000));
|
||||
view.rawInsert(sleA1);
|
||||
return true;
|
||||
};
|
||||
env.app().openLedger().modify(unauthTrustline);
|
||||
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: G1 requires authorization of A2
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)),
|
||||
ter(tecNO_AUTH));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testBrokeredAcceptOffer_UnauthorizedSeller(FeatureBitset features)
|
||||
{
|
||||
testcase(
|
||||
"Authorized broker tries to bridge offers from unauthorized "
|
||||
"seller.");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
Account broker{"broker"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, A1, A2, broker);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, limit, A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(1000)));
|
||||
env(trust(broker, limit));
|
||||
env(trust(G1, limit, broker, tfSetfAuth));
|
||||
env(pay(G1, broker, USD(1000)));
|
||||
env.close();
|
||||
|
||||
// Authorizing trustline to make an offer creation possible
|
||||
env(trust(G1, USD(0), A2, tfSetfAuth));
|
||||
env.close();
|
||||
|
||||
auto const [nftID, sellIdx] = mintAndOfferNFT(env, A2, USD(10));
|
||||
auto const buyIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(11)), token::owner(A2));
|
||||
env.close();
|
||||
|
||||
// Reseting trustline to delete it. This allows to check if
|
||||
// already existing offers handled correctly
|
||||
env(trust(A2, USD(0)));
|
||||
env.close();
|
||||
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: G1 requires authorization of broker, no trust line exists
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)),
|
||||
ter(tecNO_LINE));
|
||||
env.close();
|
||||
|
||||
// trust line created, but not authorized
|
||||
env(trust(A2, limit));
|
||||
env.close();
|
||||
|
||||
// test: G1 requires authorization of A2
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)),
|
||||
ter(tecNO_AUTH));
|
||||
env.close();
|
||||
|
||||
// test: cannot be brokered even without broker fee.
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx), ter(tecNO_AUTH));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old behavior: broker can receive IOUs without the authorization
|
||||
env(token::brokerOffers(broker, buyIdx, sellIdx),
|
||||
token::brokerFee(USD(1)));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(A2, USD) == USD(10));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testTransferFee_UnauthorizedMinter(FeatureBitset features)
|
||||
{
|
||||
testcase("Unauthorized minter receives transfer fee.");
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this, features);
|
||||
Account G1{"G1"};
|
||||
Account minter{"minter"};
|
||||
Account A1{"A1"};
|
||||
Account A2{"A2"};
|
||||
auto const USD{G1["USD"]};
|
||||
|
||||
env.fund(XRP(10000), G1, minter, A1, A2);
|
||||
env(fset(G1, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
auto const limit = USD(10000);
|
||||
|
||||
env(trust(A1, limit));
|
||||
env(trust(G1, limit, A1, tfSetfAuth));
|
||||
env(pay(G1, A1, USD(1000)));
|
||||
env(trust(A2, limit));
|
||||
env(trust(G1, limit, A2, tfSetfAuth));
|
||||
env(pay(G1, A2, USD(1000)));
|
||||
|
||||
env(trust(minter, limit));
|
||||
env.close();
|
||||
|
||||
// We authorized A1 and A2, but not the minter.
|
||||
// Now mint NFT
|
||||
auto const [nftID, minterSellIdx] =
|
||||
mintAndOfferNFT(env, minter, drops(1), 1);
|
||||
env(token::acceptSellOffer(A1, minterSellIdx));
|
||||
|
||||
uint256 const sellIdx = keylet::nftoffer(A1, env.seq(A1)).key;
|
||||
env(token::createOffer(A1, nftID, USD(100)), txflags(tfSellNFToken));
|
||||
|
||||
if (features[fixEnforceNFTokenTrustlineV2])
|
||||
{
|
||||
// test: G1 requires authorization
|
||||
env(token::acceptSellOffer(A2, sellIdx), ter(tecNO_AUTH));
|
||||
env.close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Old behavior: can sell for USD. Minter can receive tokens
|
||||
env(token::acceptSellOffer(A2, sellIdx));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT(env.balance(minter, USD) == USD(0.001));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
static FeatureBitset const all{supported_amendments()};
|
||||
|
||||
static std::array const features = {
|
||||
all - fixEnforceNFTokenTrustlineV2, all};
|
||||
|
||||
for (auto const feature : features)
|
||||
{
|
||||
testBuyOffer_UnauthorizedSeller(feature);
|
||||
testCreateBuyOffer_UnauthorizedBuyer(feature);
|
||||
testAcceptBuyOffer_UnauthorizedBuyer(feature);
|
||||
testSellOffer_UnauthorizedSeller(feature);
|
||||
testSellOffer_UnauthorizedBuyer(feature);
|
||||
testBrokeredAcceptOffer_UnauthorizedBroker(feature);
|
||||
testBrokeredAcceptOffer_UnauthorizedBuyer(feature);
|
||||
testBrokeredAcceptOffer_UnauthorizedSeller(feature);
|
||||
testTransferFee_UnauthorizedMinter(feature);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(NFTokenAuth, tx, ripple, 2);
|
||||
|
||||
} // namespace ripple
|
||||
+27
-12
@@ -5419,13 +5419,16 @@ public:
|
||||
static FeatureBitset const immediateOfferKilled{
|
||||
featureImmediateOfferKilled};
|
||||
FeatureBitset const fillOrKill{fixFillOrKill};
|
||||
FeatureBitset const permDEX{featurePermissionedDEX};
|
||||
|
||||
static std::array<FeatureBitset, 6> const feats{
|
||||
all - takerDryOffer - immediateOfferKilled,
|
||||
all - flowCross - takerDryOffer - immediateOfferKilled,
|
||||
all - flowCross - immediateOfferKilled,
|
||||
all - rmSmallIncreasedQOffers - immediateOfferKilled - fillOrKill,
|
||||
all - fillOrKill,
|
||||
static std::array<FeatureBitset, 7> const feats{
|
||||
all - takerDryOffer - immediateOfferKilled - permDEX,
|
||||
all - flowCross - takerDryOffer - immediateOfferKilled - permDEX,
|
||||
all - flowCross - immediateOfferKilled - permDEX,
|
||||
all - rmSmallIncreasedQOffers - immediateOfferKilled - fillOrKill -
|
||||
permDEX,
|
||||
all - fillOrKill - permDEX,
|
||||
all - permDEX,
|
||||
all};
|
||||
|
||||
if (BEAST_EXPECT(instance < feats.size()))
|
||||
@@ -5479,12 +5482,21 @@ class OfferWOFillOrKill_test : public OfferBaseUtil_test
|
||||
}
|
||||
};
|
||||
|
||||
class OfferWOPermDEX_test : public OfferBaseUtil_test
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
OfferBaseUtil_test::run(5);
|
||||
}
|
||||
};
|
||||
|
||||
class OfferAllFeatures_test : public OfferBaseUtil_test
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
OfferBaseUtil_test::run(5, true);
|
||||
OfferBaseUtil_test::run(6, true);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5500,14 +5512,16 @@ class Offer_manual_test : public OfferBaseUtil_test
|
||||
FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled};
|
||||
FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval};
|
||||
FeatureBitset const fillOrKill{fixFillOrKill};
|
||||
FeatureBitset const permDEX{featurePermissionedDEX};
|
||||
|
||||
testAll(all - flowCross - f1513 - immediateOfferKilled);
|
||||
testAll(all - flowCross - immediateOfferKilled);
|
||||
testAll(all - immediateOfferKilled - fillOrKill);
|
||||
testAll(all - fillOrKill);
|
||||
testAll(all - flowCross - f1513 - immediateOfferKilled - permDEX);
|
||||
testAll(all - flowCross - immediateOfferKilled - permDEX);
|
||||
testAll(all - immediateOfferKilled - fillOrKill - permDEX);
|
||||
testAll(all - fillOrKill - permDEX);
|
||||
testAll(all - permDEX);
|
||||
testAll(all);
|
||||
|
||||
testAll(all - flowCross - takerDryOffer);
|
||||
testAll(all - flowCross - takerDryOffer - permDEX);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5516,6 +5530,7 @@ BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFlowCross, tx, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferWTakerDryOffer, tx, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOSmallQOffers, tx, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOFillOrKill, tx, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferWOPermDEX, tx, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(OfferAllFeatures, tx, ripple, 2);
|
||||
BEAST_DEFINE_TESTSUITE_MANUAL_PRIO(Offer_manual, tx, ripple, 20);
|
||||
|
||||
|
||||
+851
-120
File diff suppressed because it is too large.
Load diff
@@ -27,6 +27,9 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/basics/safe_cast.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
@@ -656,6 +659,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
env.app().logs().journal("Flow"));
|
||||
BEAST_EXPECT(ter == expTer);
|
||||
if (sizeof...(expSteps) != 0)
|
||||
@@ -684,6 +688,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
env.app().logs().journal("Flow"));
|
||||
(void)_;
|
||||
BEAST_EXPECT(ter == tesSUCCESS);
|
||||
@@ -701,6 +706,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
env.app().logs().journal("Flow"));
|
||||
(void)_;
|
||||
BEAST_EXPECT(ter == tesSUCCESS);
|
||||
@@ -738,7 +744,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
STPath(),
|
||||
tesSUCCESS,
|
||||
D{alice, gw, usdC},
|
||||
B{USD, EUR},
|
||||
B{USD, EUR, std::nullopt},
|
||||
D{gw, bob, eurC});
|
||||
|
||||
// Path with explicit offer
|
||||
@@ -749,7 +755,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
STPath({ipe(EUR)}),
|
||||
tesSUCCESS,
|
||||
D{alice, gw, usdC},
|
||||
B{USD, EUR},
|
||||
B{USD, EUR, std::nullopt},
|
||||
D{gw, bob, eurC});
|
||||
|
||||
// Path with offer that changes issuer only
|
||||
@@ -761,7 +767,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
STPath({iape(carol)}),
|
||||
tesSUCCESS,
|
||||
D{alice, gw, usdC},
|
||||
B{USD, carol["USD"]},
|
||||
B{USD, carol["USD"], std::nullopt},
|
||||
D{carol, bob, usdC});
|
||||
|
||||
// Path with XRP src currency
|
||||
@@ -772,7 +778,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
STPath({ipe(USD)}),
|
||||
tesSUCCESS,
|
||||
XRPS{alice},
|
||||
B{XRP, USD},
|
||||
B{XRP, USD, std::nullopt},
|
||||
D{gw, bob, usdC});
|
||||
|
||||
// Path with XRP dst currency.
|
||||
@@ -787,7 +793,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
xrpAccount()}}),
|
||||
tesSUCCESS,
|
||||
D{alice, gw, usdC},
|
||||
B{USD, XRP},
|
||||
B{USD, XRP, std::nullopt},
|
||||
XRPS{bob});
|
||||
|
||||
// Path with XRP cross currency bridged payment
|
||||
@@ -798,8 +804,8 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
STPath({cpe(xrpCurrency())}),
|
||||
tesSUCCESS,
|
||||
D{alice, gw, usdC},
|
||||
B{USD, XRP},
|
||||
B{XRP, EUR},
|
||||
B{USD, XRP, std::nullopt},
|
||||
B{XRP, EUR, std::nullopt},
|
||||
D{gw, bob, eurC});
|
||||
|
||||
// XRP -> XRP transaction can't include a path
|
||||
@@ -821,6 +827,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
flowJournal);
|
||||
BEAST_EXPECT(r.first == temBAD_PATH);
|
||||
}
|
||||
@@ -837,6 +844,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
flowJournal);
|
||||
BEAST_EXPECT(r.first == temBAD_PATH);
|
||||
}
|
||||
@@ -853,6 +861,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
flowJournal);
|
||||
BEAST_EXPECT(r.first == temBAD_PATH);
|
||||
}
|
||||
@@ -990,6 +999,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
true,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
env.app().logs().journal("Flow"));
|
||||
BEAST_EXPECT(ter == tesSUCCESS);
|
||||
BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
|
||||
@@ -1017,12 +1027,13 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
false,
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
env.app().logs().journal("Flow"));
|
||||
BEAST_EXPECT(ter == tesSUCCESS);
|
||||
BEAST_EXPECT(equal(
|
||||
strand,
|
||||
D{alice, gw, usdC},
|
||||
B{USD.issue(), xrpIssue()},
|
||||
B{USD.issue(), xrpIssue(), std::nullopt},
|
||||
XRPS{bob}));
|
||||
}
|
||||
}
|
||||
@@ -1201,6 +1212,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
dstAcc,
|
||||
noAccount(),
|
||||
pathSet,
|
||||
std::nullopt,
|
||||
env.app().logs(),
|
||||
&inputs);
|
||||
BEAST_EXPECT(r.result() == temBAD_PATH);
|
||||
@@ -1213,6 +1225,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
noAccount(),
|
||||
srcAcc,
|
||||
pathSet,
|
||||
std::nullopt,
|
||||
env.app().logs(),
|
||||
&inputs);
|
||||
BEAST_EXPECT(r.result() == temBAD_PATH);
|
||||
@@ -1225,6 +1238,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
dstAcc,
|
||||
srcAcc,
|
||||
pathSet,
|
||||
std::nullopt,
|
||||
env.app().logs(),
|
||||
&inputs);
|
||||
BEAST_EXPECT(r.result() == temBAD_PATH);
|
||||
@@ -1237,6 +1251,7 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
dstAcc,
|
||||
srcAcc,
|
||||
pathSet,
|
||||
std::nullopt,
|
||||
env.app().logs(),
|
||||
&inputs);
|
||||
BEAST_EXPECT(r.result() == temBAD_PATH);
|
||||
@@ -1253,13 +1268,16 @@ struct PayStrand_test : public beast::unit_test::suite
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testToStrand(sa - featureFlowCross);
|
||||
testToStrand(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testToStrand(sa - featurePermissionedDEX);
|
||||
testToStrand(sa);
|
||||
|
||||
testRIPD1373(sa - featureFlowCross);
|
||||
testRIPD1373(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testRIPD1373(sa - featurePermissionedDEX);
|
||||
testRIPD1373(sa);
|
||||
|
||||
testLoop(sa - featureFlowCross);
|
||||
testLoop(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testLoop(sa - featurePermissionedDEX);
|
||||
testLoop(sa);
|
||||
|
||||
testNoAccount(sa);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -50,12 +50,6 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
obj[sfReserveIncrement] = 0;
|
||||
obj[sfReferenceFeeUnits] = 0;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfExtensionComputeLimit] = 0;
|
||||
obj[sfExtensionSizeLimit] = 0;
|
||||
obj[sfGasPrice] = 0;
|
||||
}
|
||||
}));
|
||||
|
||||
res.emplace_back(STTx(ttAMENDMENT, [&](auto& obj) {
|
||||
@@ -122,10 +116,9 @@ struct PseudoTx_test : public beast::unit_test::suite
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const xrpFees{featureXRPFees};
|
||||
|
||||
testPrevented(all - featureXRPFees - featureSmartEscrow);
|
||||
testPrevented(all - featureXRPFees);
|
||||
testPrevented(all - featureSmartEscrow);
|
||||
testPrevented(all);
|
||||
testAllowed();
|
||||
}
|
||||
|
||||
@@ -75,7 +75,8 @@ struct SetAuth_test : public beast::unit_test::suite
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testAuth(sa - featureFlowCross);
|
||||
testAuth(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testAuth(sa - featurePermissionedDEX);
|
||||
testAuth(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -267,6 +267,7 @@ class TheoreticalQuality_test : public beast::unit_test::suite
|
||||
sb.rules().enabled(featureOwnerPaysFee),
|
||||
OfferCrossing::no,
|
||||
ammContext,
|
||||
std::nullopt,
|
||||
dummyJ);
|
||||
|
||||
BEAST_EXPECT(sr.first == tesSUCCESS);
|
||||
|
||||
@@ -481,7 +481,8 @@ public:
|
||||
|
||||
using namespace test::jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testWithFeatures(sa - featureFlowCross);
|
||||
testWithFeatures(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testWithFeatures(sa - featurePermissionedDEX);
|
||||
testWithFeatures(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,10 +53,11 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
class Vault_test : public beast::unit_test::suite
|
||||
{
|
||||
using PrettyAsset = ripple::test::jtx::PrettyAsset;
|
||||
using PrettyAmount = ripple::test::jtx::PrettyAmount;
|
||||
|
||||
static auto constexpr negativeAmount =
|
||||
[](PrettyAsset const& asset) -> PrettyAmount {
|
||||
return {STAmount{asset.raw(), 1ul, 0, true, STAmount::unchecked{}}, ""};
|
||||
@@ -1210,6 +1211,7 @@ class Vault_test : public beast::unit_test::suite
|
||||
testCreateFailMPT()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, supported_amendments() | featureSingleAssetVault};
|
||||
Account issuer{"issuer"};
|
||||
Account owner{"owner"};
|
||||
@@ -1231,6 +1233,7 @@ class Vault_test : public beast::unit_test::suite
|
||||
testNonTransferableShares()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this, supported_amendments() | featureSingleAssetVault};
|
||||
Account issuer{"issuer"};
|
||||
Account owner{"owner"};
|
||||
@@ -1787,6 +1790,8 @@ class Vault_test : public beast::unit_test::suite
|
||||
void
|
||||
testWithIOU()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
auto testCase =
|
||||
[&, this](
|
||||
std::function<void(
|
||||
@@ -2242,6 +2247,8 @@ class Vault_test : public beast::unit_test::suite
|
||||
void
|
||||
testWithDomainCheck()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testcase("private vault");
|
||||
|
||||
Env env{*this, supported_amendments() | featureSingleAssetVault};
|
||||
@@ -2525,6 +2532,8 @@ class Vault_test : public beast::unit_test::suite
|
||||
void
|
||||
testWithDomainCheckXRP()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testcase("private XRP vault");
|
||||
|
||||
Env env{*this, supported_amendments() | featureSingleAssetVault};
|
||||
@@ -2656,6 +2665,8 @@ class Vault_test : public beast::unit_test::suite
|
||||
void
|
||||
testRPC()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testcase("RPC");
|
||||
Env env{*this, supported_amendments() | featureSingleAssetVault};
|
||||
Account const owner{"owner"};
|
||||
|
||||
@@ -1,801 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/misc/WasmHostFunc.h>
|
||||
#include <xrpld/app/misc/WasmVM.h>
|
||||
|
||||
#include <iwasm/wasm_c_api.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
// #define DEBUG_OUTPUT 1
|
||||
#endif
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
using Add_proto = int32_t(int32_t, int32_t);
|
||||
static wasm_trap_t*
|
||||
Add(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
int32_t Val1 = params->data[0].of.i32;
|
||||
int32_t Val2 = params->data[1].of.i32;
|
||||
// printf("Host function \"Add\": %d + %d\n", Val1, Val2);
|
||||
results->data[0] = WASM_I32_VAL(Val1 + Val2);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct TestLedgerDataProvider
|
||||
{
|
||||
jtx::Env* env;
|
||||
|
||||
public:
|
||||
TestLedgerDataProvider(jtx::Env* env_) : env(env_)
|
||||
{
|
||||
}
|
||||
|
||||
int32_t
|
||||
get_ledger_sqn()
|
||||
{
|
||||
return (int32_t)env->current()->seq();
|
||||
}
|
||||
};
|
||||
|
||||
using getLedgerSqn_proto = std::int32_t();
|
||||
static wasm_trap_t*
|
||||
getLedgerSqn_wrap(void* env, wasm_val_vec_t const*, wasm_val_vec_t* results)
|
||||
{
|
||||
auto sqn = reinterpret_cast<TestLedgerDataProvider*>(env)->get_ledger_sqn();
|
||||
|
||||
results->data[0] = WASM_I32_VAL(sqn);
|
||||
results->num_elems = 1;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct TestHostFunctionsOld : public HostFunctions
|
||||
{
|
||||
test::jtx::Env* env_;
|
||||
Bytes accountID_;
|
||||
Bytes data_;
|
||||
int clock_drift_ = 0;
|
||||
test::StreamSink sink_;
|
||||
beast::Journal jlog_;
|
||||
void const* rt_ = nullptr;
|
||||
|
||||
public:
|
||||
explicit TestHostFunctionsOld(test::jtx::Env* env, int cd = 0)
|
||||
: env_(env)
|
||||
, clock_drift_(cd)
|
||||
, sink_(beast::severities::kDebug)
|
||||
, jlog_(sink_)
|
||||
{
|
||||
std::string s = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
accountID_ = Bytes{s.begin(), s.end()};
|
||||
std::string t = "10000";
|
||||
data_ = Bytes{t.begin(), t.end()};
|
||||
}
|
||||
|
||||
virtual void
|
||||
setRT(void const* rt) override
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
virtual void const*
|
||||
getRT() const override
|
||||
{
|
||||
return rt_;
|
||||
}
|
||||
|
||||
test::StreamSink&
|
||||
getSink()
|
||||
{
|
||||
return sink_;
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
getJournal() override
|
||||
{
|
||||
return jlog_;
|
||||
}
|
||||
|
||||
int32_t
|
||||
getLedgerSqn() override
|
||||
{
|
||||
return (int32_t)env_->current()->seq();
|
||||
}
|
||||
|
||||
int32_t
|
||||
getParentLedgerTime() override
|
||||
{
|
||||
return env_->current()->parentCloseTime().time_since_epoch().count() +
|
||||
clock_drift_;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getTxField(SField const& fname) override
|
||||
{
|
||||
return accountID_;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) override
|
||||
{
|
||||
return data_;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getCurrentLedgerObjField(SField const& fname) override
|
||||
{
|
||||
if (fname.getName() == "Destination" || fname.getName() == "Account")
|
||||
return accountID_;
|
||||
else if (fname.getName() == "Data")
|
||||
return data_;
|
||||
else if (fname.getName() == "FinishAfter")
|
||||
{
|
||||
auto t =
|
||||
env_->current()->parentCloseTime().time_since_epoch().count();
|
||||
std::string s = std::to_string(t);
|
||||
return Bytes{s.begin(), s.end()};
|
||||
}
|
||||
|
||||
return Unexpected(-1);
|
||||
}
|
||||
};
|
||||
|
||||
struct TestHostFunctions : public HostFunctions
|
||||
{
|
||||
test::jtx::Env& env_;
|
||||
AccountID accountID_;
|
||||
Bytes data_;
|
||||
int clock_drift_ = 0;
|
||||
void const* rt_ = nullptr;
|
||||
|
||||
public:
|
||||
explicit TestHostFunctions(test::jtx::Env& env, int cd = 0)
|
||||
: env_(env), clock_drift_(cd)
|
||||
{
|
||||
auto opt = parseBase58<AccountID>("rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh");
|
||||
if (opt)
|
||||
accountID_ = *opt;
|
||||
std::string t = "10000";
|
||||
data_ = Bytes{t.begin(), t.end()};
|
||||
}
|
||||
|
||||
virtual void
|
||||
setRT(void const* rt) override
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
virtual void const*
|
||||
getRT() const override
|
||||
{
|
||||
return rt_;
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
getJournal() override
|
||||
{
|
||||
return env_.journal;
|
||||
}
|
||||
|
||||
int32_t
|
||||
getLedgerSqn() override
|
||||
{
|
||||
return static_cast<int32_t>(env_.current()->seq());
|
||||
}
|
||||
|
||||
int32_t
|
||||
getParentLedgerTime() override
|
||||
{
|
||||
return env_.current()->parentCloseTime().time_since_epoch().count() +
|
||||
clock_drift_;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx) override
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getTxField(SField const& fname) override
|
||||
{
|
||||
if (fname == sfAccount)
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
else if (fname == sfFee)
|
||||
{
|
||||
int64_t x = 235;
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
|
||||
return Bytes{p, p + sizeof(x)};
|
||||
}
|
||||
else if (fname == sfSequence)
|
||||
{
|
||||
int32_t x = getLedgerSqn();
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
|
||||
return Bytes{p, p + sizeof(x)};
|
||||
}
|
||||
return Bytes();
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getTxNestedField(Slice const& locator) override
|
||||
{
|
||||
uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41,
|
||||
0xbf, 0x49, 0xd2, 0x45, 0x9f, 0xa4, 0xa0, 0x34,
|
||||
0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92, 0xfc, 0xee,
|
||||
0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00};
|
||||
return Bytes(&a[0], &a[sizeof(a)]);
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) override
|
||||
{
|
||||
// auto const& sn = fname.getName();
|
||||
if (fname == sfBalance)
|
||||
{
|
||||
int64_t x = 10'000;
|
||||
uint8_t const* p = reinterpret_cast<uint8_t const*>(&x);
|
||||
return Bytes{p, p + sizeof(x)};
|
||||
}
|
||||
return data_;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getCurrentLedgerObjField(SField const& fname) override
|
||||
{
|
||||
auto const& sn = fname.getName();
|
||||
if (sn == "Destination" || sn == "Account")
|
||||
return Bytes(accountID_.begin(), accountID_.end());
|
||||
else if (sn == "Data")
|
||||
return data_;
|
||||
else if (sn == "FinishAfter")
|
||||
{
|
||||
auto t =
|
||||
env_.current()->parentCloseTime().time_since_epoch().count();
|
||||
std::string s = std::to_string(t);
|
||||
return Bytes{s.begin(), s.end()};
|
||||
}
|
||||
|
||||
return Unexpected(-1);
|
||||
}
|
||||
|
||||
int32_t
|
||||
getTxArrayLen(SField const& fname) override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
int32_t
|
||||
getTxNestedArrayLen(Slice const& locator) override
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
int32_t
|
||||
updateData(Bytes const& data) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
accountKeylet(AccountID const& account) override
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HF_ERR_INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::account(account);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
int32_t
|
||||
trace(std::string const& msg, Bytes const& data, bool asHex) override
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
auto j = getJournal().trace();
|
||||
#endif
|
||||
j << msg;
|
||||
if (!asHex)
|
||||
j << std::string_view(
|
||||
reinterpret_cast<char const*>(data.data()), data.size());
|
||||
else
|
||||
{
|
||||
auto const hex =
|
||||
boost::algorithm::hex(std::string(data.begin(), data.end()));
|
||||
j << hex;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
|
||||
return msg.size() + data.size() * (asHex ? 2 : 1);
|
||||
}
|
||||
|
||||
int32_t
|
||||
traceNum(std::string const& msg, int64_t data) override
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto& j = std::cerr;
|
||||
#else
|
||||
auto j = getJournal().trace();
|
||||
#endif
|
||||
j << msg << data;
|
||||
|
||||
#ifdef DEBUG_OUTPUT
|
||||
j << std::endl;
|
||||
#endif
|
||||
return msg.size() + sizeof(data);
|
||||
}
|
||||
};
|
||||
|
||||
struct Wasm_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testWasmFib()
|
||||
{
|
||||
testcase("Wasm fibo");
|
||||
|
||||
auto const ws = boost::algorithm::unhex(fib32Hex);
|
||||
Bytes const wasm(ws.begin(), ws.end());
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto const r = engine.run(wasm, "fib", wasmParams(10));
|
||||
|
||||
BEAST_EXPECT(r.has_value() && (r->result == 55));
|
||||
}
|
||||
|
||||
void
|
||||
testWasmSha()
|
||||
{
|
||||
testcase("Wasm sha");
|
||||
|
||||
auto const ws = boost::algorithm::unhex(sha512PureHex);
|
||||
Bytes const wasm(ws.begin(), ws.end());
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto const r =
|
||||
engine.run(wasm, "sha512_process", wasmParams(sha512PureHex));
|
||||
|
||||
BEAST_EXPECT(r.has_value() && (r->result == 34432));
|
||||
}
|
||||
|
||||
void
|
||||
testWasmB58()
|
||||
{
|
||||
testcase("Wasm base58");
|
||||
auto const ws = boost::algorithm::unhex(b58Hex);
|
||||
Bytes const wasm(ws.begin(), ws.end());
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
Bytes outb;
|
||||
outb.resize(1024);
|
||||
|
||||
auto const minsz = std::min(
|
||||
static_cast<std::uint32_t>(512),
|
||||
static_cast<std::uint32_t>(b58Hex.size()));
|
||||
auto const s = std::string_view(b58Hex.c_str(), minsz);
|
||||
auto const r = engine.run(wasm, "b58enco", wasmParams(outb, s));
|
||||
|
||||
BEAST_EXPECT(r.has_value() && r->result);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmSP1Verifier()
|
||||
{
|
||||
testcase("Wasm sp1 zkproof verifier");
|
||||
auto const ws = boost::algorithm::unhex(sp1_wasm);
|
||||
Bytes const wasm(ws.begin(), ws.end());
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto const r = engine.run(wasm, "sp1_groth16_verifier");
|
||||
|
||||
BEAST_EXPECT(r.has_value() && r->result);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmBG16Verifier()
|
||||
{
|
||||
testcase("Wasm BG16 zkproof verifier");
|
||||
auto const ws = boost::algorithm::unhex(zkProofHex);
|
||||
Bytes const wasm(ws.begin(), ws.end());
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto const r = engine.run(wasm, "bellman_groth16_test");
|
||||
|
||||
BEAST_EXPECT(r.has_value() && r->result);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmLedgerSqn()
|
||||
{
|
||||
testcase("Wasm get ledger sequence");
|
||||
|
||||
auto wasmStr = boost::algorithm::unhex(ledgerSqnHex);
|
||||
Bytes wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env{*this};
|
||||
TestLedgerDataProvider ledgerDataProvider(&env);
|
||||
std::string const funcName("finish");
|
||||
|
||||
std::vector<WasmImportFunc> imports;
|
||||
WASM_IMPORT_FUNC(imports, getLedgerSqn, &ledgerDataProvider);
|
||||
|
||||
auto& engine = WasmEngine::instance();
|
||||
|
||||
auto r = engine.run(
|
||||
wasm, funcName, {}, imports, nullptr, 1'000'000, env.journal);
|
||||
if (BEAST_EXPECT(r.has_value()))
|
||||
BEAST_EXPECT(!r->result);
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// empty module - run the same module
|
||||
r = engine.run(
|
||||
{}, funcName, {}, imports, nullptr, 1'000'000, env.journal);
|
||||
if (BEAST_EXPECT(r.has_value()))
|
||||
BEAST_EXPECT(r->result);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmCheckJson()
|
||||
{
|
||||
testcase("Wasm check json");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
auto const wasmStr = boost::algorithm::unhex(checkJsonHex);
|
||||
Bytes const wasm(wasmStr.begin(), wasmStr.end());
|
||||
std::string const funcName("check_accountID");
|
||||
{
|
||||
std::string str = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
Bytes data(str.begin(), str.end());
|
||||
auto re = runEscrowWasm(
|
||||
wasm, funcName, wasmParams(data), nullptr, -1, env.journal);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
BEAST_EXPECT(re.value().result);
|
||||
}
|
||||
{
|
||||
std::string str = "rHb9CJAWyB4rj91VRWn96DkukG4bwdty00";
|
||||
Bytes data(str.begin(), str.end());
|
||||
auto re = runEscrowWasm(
|
||||
wasm, funcName, wasmParams(data), nullptr, -1, env.journal);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
BEAST_EXPECT(!re.value().result);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWasmCompareJson()
|
||||
{
|
||||
testcase("Wasm compare json");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
auto wasmStr = boost::algorithm::unhex(compareJsonHex);
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
std::string funcName("compare_accountID");
|
||||
|
||||
std::vector<uint8_t> const tx_data(tx_js.begin(), tx_js.end());
|
||||
std::vector<uint8_t> const lo_data(lo_js.begin(), lo_js.end());
|
||||
auto re = runEscrowWasm(
|
||||
wasm,
|
||||
funcName,
|
||||
wasmParams(tx_data, lo_data),
|
||||
nullptr,
|
||||
-1,
|
||||
env.journal);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
BEAST_EXPECT(re.value().result);
|
||||
|
||||
std::vector<uint8_t> const lo_data2(lo_js2.begin(), lo_js2.end());
|
||||
re = runEscrowWasm(
|
||||
wasm,
|
||||
funcName,
|
||||
wasmParams(tx_data, lo_data2),
|
||||
nullptr,
|
||||
-1,
|
||||
env.journal);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
BEAST_EXPECT(!re.value().result);
|
||||
}
|
||||
|
||||
void
|
||||
testWasmLib()
|
||||
{
|
||||
testcase("wasmtime lib test");
|
||||
// clang-format off
|
||||
/* The WASM module buffer. */
|
||||
Bytes const wasm = {/* WASM header */
|
||||
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
|
||||
/* Type section */
|
||||
0x01, 0x07, 0x01,
|
||||
/* function type {i32, i32} -> {i32} */
|
||||
0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
|
||||
/* Import section */
|
||||
0x02, 0x13, 0x01,
|
||||
/* module name: "extern" */
|
||||
0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
|
||||
/* extern name: "func-add" */
|
||||
0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
|
||||
/* import desc: func 0 */
|
||||
0x00, 0x00,
|
||||
/* Function section */
|
||||
0x03, 0x02, 0x01, 0x00,
|
||||
/* Export section */
|
||||
0x07, 0x0A, 0x01,
|
||||
/* export name: "addTwo" */
|
||||
0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
|
||||
/* export desc: func 0 */
|
||||
0x00, 0x01,
|
||||
/* Code section */
|
||||
0x0A, 0x0A, 0x01,
|
||||
/* code body */
|
||||
0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B};
|
||||
// clang-format on
|
||||
auto& vm = WasmEngine::instance();
|
||||
|
||||
std::vector<WasmImportFunc> imports;
|
||||
WasmImpFunc<Add_proto>(
|
||||
imports, "func-add", reinterpret_cast<void*>(&Add));
|
||||
|
||||
auto res = vm.run(wasm, "addTwo", wasmParams(1234, 5678), imports);
|
||||
|
||||
// if (res) printf("invokeAdd get the result: %d\n", res.value());
|
||||
|
||||
BEAST_EXPECT(res.has_value() && res->result == 6912);
|
||||
}
|
||||
|
||||
void
|
||||
testBadWasm()
|
||||
{
|
||||
testcase("bad wasm test");
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
HostFunctions hfs;
|
||||
auto wasmHex = "00000000";
|
||||
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
std::string funcName("mock_escrow");
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &hfs, 15, env.journal);
|
||||
BEAST_EXPECT(re.error());
|
||||
}
|
||||
|
||||
void
|
||||
testEscrowWasmDN1()
|
||||
{
|
||||
testcase("escrow wasm devnet 1 test");
|
||||
std::string const wasmHex = allHostFunctionsHex;
|
||||
|
||||
std::string const wasmStr = boost::algorithm::unhex(wasmHex);
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
// let sender = get_tx_account_id();
|
||||
// let owner = get_current_escrow_account_id();
|
||||
// let dest = get_current_escrow_destination();
|
||||
// let dest_balance = get_account_balance(dest);
|
||||
// let escrow_data = get_current_escrow_data();
|
||||
// let ed_str = String::from_utf8(escrow_data).unwrap();
|
||||
// let threshold_balance = ed_str.parse::<u64>().unwrap();
|
||||
// let pl_time = host_lib::getParentLedgerTime();
|
||||
// let e_time = get_current_escrow_finish_after();
|
||||
// sender == owner && dest_balance <= threshold_balance &&
|
||||
// pl_time >= e_time
|
||||
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
{
|
||||
TestHostFunctions nfs(env, 0);
|
||||
std::string funcName("finish");
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re.value().result);
|
||||
// std::cout << "good case result " << re.value().result
|
||||
// << " cost: " << re.value().cost << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
{ // fail because current time < escrow_finish_after time
|
||||
TestHostFunctions nfs(env, -1);
|
||||
std::string funcName("finish");
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(!re.value().result);
|
||||
// std::cout << "bad case (current time < escrow_finish_after "
|
||||
// "time) result "
|
||||
// << re.value().result << " cost: " <<
|
||||
// re.value().cost
|
||||
// << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
{ // fail because trying to access nonexistent field
|
||||
struct BadTestHostFunctions : public TestHostFunctions
|
||||
{
|
||||
explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env)
|
||||
{
|
||||
}
|
||||
Expected<Bytes, int32_t>
|
||||
getTxField(SField const& fname) override
|
||||
{
|
||||
return Unexpected(-1);
|
||||
}
|
||||
};
|
||||
BadTestHostFunctions nfs(env);
|
||||
std::string funcName("finish");
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
|
||||
BEAST_EXPECT(re.error());
|
||||
// std::cout << "bad case (access nonexistent field) result "
|
||||
// << re.error() << std::endl;
|
||||
}
|
||||
|
||||
{ // fail because trying to allocate more than MAX_PAGES memory
|
||||
struct BadTestHostFunctions : public TestHostFunctions
|
||||
{
|
||||
explicit BadTestHostFunctions(Env& env) : TestHostFunctions(env)
|
||||
{
|
||||
}
|
||||
Expected<Bytes, int32_t>
|
||||
getTxField(SField const& fname) override
|
||||
{
|
||||
return Bytes((MAX_PAGES + 1) * 64 * 1024, 1);
|
||||
}
|
||||
};
|
||||
BadTestHostFunctions nfs(env);
|
||||
std::string funcName("finish");
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100000);
|
||||
BEAST_EXPECT(!re);
|
||||
// std::cout << "bad case (more than MAX_PAGES) result "
|
||||
// << re.error() << std::endl;
|
||||
}
|
||||
|
||||
{ // fail because recursion too deep
|
||||
auto wasmHex = deepRecursionHex;
|
||||
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
TestHostFunctionsOld nfs(&env);
|
||||
std::string funcName("recursive");
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 1000'000'000);
|
||||
BEAST_EXPECT(re.error());
|
||||
// std::cout << "bad case (deep recursion) result " << re.error()
|
||||
// << std::endl;
|
||||
|
||||
auto const& sink = nfs.getSink();
|
||||
auto countSubstr = [](std::string const& str,
|
||||
std::string const& substr) {
|
||||
std::size_t pos = 0;
|
||||
int occurrences = 0;
|
||||
while ((pos = str.find(substr, pos)) != std::string::npos)
|
||||
{
|
||||
occurrences++;
|
||||
pos += substr.length();
|
||||
}
|
||||
return occurrences;
|
||||
};
|
||||
|
||||
BEAST_EXPECT(
|
||||
countSubstr(
|
||||
sink.messages().str(), "WAMR error: failed to call func") ==
|
||||
1);
|
||||
BEAST_EXPECT(
|
||||
countSubstr(
|
||||
sink.messages().str(),
|
||||
"WAMR Exception: wasm operand stack overflow") == 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testEscrowWasmDN2()
|
||||
{
|
||||
testcase("wasm devnet 3 test");
|
||||
|
||||
std::string const funcName("finish");
|
||||
|
||||
using namespace test::jtx;
|
||||
|
||||
Env env(*this);
|
||||
{
|
||||
std::string const wasmHex = xrplStdExampleHex;
|
||||
std::string const wasmStr = boost::algorithm::unhex(wasmHex);
|
||||
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
TestHostFunctions nfs(env, 0);
|
||||
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100'000);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re->result);
|
||||
// std::cout << "good case result " << re.value().result
|
||||
// << " cost: " << re.value().cost << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
{
|
||||
std::string const wasmHex = hostFunctions2Hex;
|
||||
std::string const wasmStr = boost::algorithm::unhex(wasmHex);
|
||||
std::vector<uint8_t> const wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
TestHostFunctions nfs(env, 0);
|
||||
|
||||
auto re = runEscrowWasm(wasm, funcName, {}, &nfs, 100'000);
|
||||
if (BEAST_EXPECT(re.has_value()))
|
||||
{
|
||||
BEAST_EXPECT(re->result);
|
||||
// std::cout << "good case result " << re.value().result
|
||||
// << " cost: " << re.value().cost << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
using namespace test::jtx;
|
||||
|
||||
testWasmLib();
|
||||
testBadWasm();
|
||||
testWasmCheckJson();
|
||||
testWasmCompareJson();
|
||||
testWasmLedgerSqn();
|
||||
|
||||
testWasmFib();
|
||||
testWasmSha();
|
||||
testWasmB58();
|
||||
|
||||
// runing too long
|
||||
// testWasmSP1Verifier();
|
||||
// testWasmBG16Verifier();
|
||||
|
||||
// TODO: needs fix for new host functions interface
|
||||
// testEscrowWasmDN1();
|
||||
testEscrowWasmDN2();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Wasm, app, ripple);
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
File diff suppressed because it is too large.
Load diff
@@ -68,8 +68,8 @@ class ByzantineFailureSim_test : public beast::unit_test::suite
|
||||
for (TrustGraph<Peer*>::ForkInfo const& fi :
|
||||
sim.trustGraph.forkablePairs(0.8))
|
||||
{
|
||||
std::cout << "Can fork " << PeerGroup{fi.unlA} << " " << " "
|
||||
<< PeerGroup{fi.unlB} << " overlap " << fi.overlap
|
||||
std::cout << "Can fork " << PeerGroup{fi.unlA} << " "
|
||||
<< " " << PeerGroup{fi.unlB} << " overlap " << fi.overlap
|
||||
<< " required " << fi.required << "\n";
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@
|
||||
#include <test/jtx/delivermin.h>
|
||||
#include <test/jtx/deposit.h>
|
||||
#include <test/jtx/did.h>
|
||||
#include <test/jtx/domain.h>
|
||||
#include <test/jtx/escrow.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/invoice_id.h>
|
||||
@@ -52,6 +54,7 @@
|
||||
#include <test/jtx/owners.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
#include <test/jtx/permissioned_dex.h>
|
||||
#include <test/jtx/permissioned_domains.h>
|
||||
#include <test/jtx/prop.h>
|
||||
#include <test/jtx/quality.h>
|
||||
|
||||
+10
-1
@@ -127,7 +127,6 @@ class AMM
|
||||
STAmount const asset1_;
|
||||
STAmount const asset2_;
|
||||
uint256 const ammID_;
|
||||
IOUAmount const initialLPTokens_;
|
||||
bool log_;
|
||||
bool doClose_;
|
||||
// Predict next purchase price
|
||||
@@ -140,6 +139,7 @@ class AMM
|
||||
std::uint32_t const fee_;
|
||||
AccountID const ammAccount_;
|
||||
Issue const lptIssue_;
|
||||
IOUAmount const initialLPTokens_;
|
||||
|
||||
public:
|
||||
AMM(Env& env,
|
||||
@@ -196,6 +196,12 @@ public:
|
||||
Issue const& issue2,
|
||||
std::optional<AccountID> const& account = std::nullopt) const;
|
||||
|
||||
std::tuple<STAmount, STAmount, STAmount>
|
||||
balances(std::optional<AccountID> const& account = std::nullopt) const
|
||||
{
|
||||
return balances(asset1_.get<Issue>(), asset2_.get<Issue>(), account);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
expectLPTokens(AccountID const& account, IOUAmount const& tokens) const;
|
||||
|
||||
@@ -430,6 +436,9 @@ private:
|
||||
|
||||
[[nodiscard]] bool
|
||||
expectAuctionSlot(auto&& cb) const;
|
||||
|
||||
IOUAmount
|
||||
initialTokens();
|
||||
};
|
||||
|
||||
namespace amm {
|
||||
|
||||
@@ -35,6 +35,15 @@ class AMM;
|
||||
|
||||
enum class Fund { All, Acct, Gw, IOUOnly };
|
||||
|
||||
struct TestAMMArg
|
||||
{
|
||||
std::optional<std::pair<STAmount, STAmount>> pool = std::nullopt;
|
||||
std::uint16_t tfee = 0;
|
||||
std::optional<jtx::ter> ter = std::nullopt;
|
||||
std::vector<FeatureBitset> features = {supported_amendments()};
|
||||
bool noLog = false;
|
||||
};
|
||||
|
||||
void
|
||||
fund(
|
||||
jtx::Env& env,
|
||||
@@ -87,6 +96,11 @@ protected:
|
||||
std::uint16_t tfee = 0,
|
||||
std::optional<jtx::ter> const& ter = std::nullopt,
|
||||
std::vector<FeatureBitset> const& features = {supported_amendments()});
|
||||
|
||||
void
|
||||
testAMM(
|
||||
std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
|
||||
TestAMMArg const& arg);
|
||||
};
|
||||
|
||||
class AMMTest : public jtx::AMMTestBase
|
||||
|
||||
@@ -472,6 +472,15 @@ public:
|
||||
PrettyAmount
|
||||
balance(Account const& account, Issue const& issue) const;
|
||||
|
||||
PrettyAmount
|
||||
balance(Account const& account, MPTIssue const& mptIssue) const;
|
||||
|
||||
/** Returns the IOU limit on an account.
|
||||
Returns 0 if the trust line does not exist.
|
||||
*/
|
||||
PrettyAmount
|
||||
limit(Account const& account, Issue const& issue) const;
|
||||
|
||||
/** Return the number of objects owned by an account.
|
||||
* Returns 0 if the account does not exist.
|
||||
*/
|
||||
@@ -622,6 +631,12 @@ public:
|
||||
void
|
||||
disableFeature(uint256 const feature);
|
||||
|
||||
bool
|
||||
enabled(uint256 feature) const
|
||||
{
|
||||
return current()->rules().enabled(feature);
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
fund(bool setDefaultRipple, STAmount const& amount, Account const& account);
|
||||
|
||||
@@ -233,197 +233,6 @@ expectLedgerEntryRoot(
|
||||
Account const& acct,
|
||||
STAmount const& expectedValue);
|
||||
|
||||
/* Escrow */
|
||||
/******************************************************************************/
|
||||
|
||||
Json::Value
|
||||
escrow(AccountID const& account, AccountID const& to, STAmount const& amount);
|
||||
|
||||
inline Json::Value
|
||||
escrow(Account const& account, Account const& to, STAmount const& amount)
|
||||
{
|
||||
return escrow(account.id(), to.id(), amount);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
finish(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return finish(account.id(), from.id(), seq);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
cancel(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return cancel(account.id(), from, seq);
|
||||
}
|
||||
|
||||
std::array<std::uint8_t, 39> constexpr cb1 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC,
|
||||
0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
|
||||
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95,
|
||||
0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55, 0x81, 0x01, 0x00}};
|
||||
|
||||
// A PreimageSha256 fulfillments and its associated condition.
|
||||
std::array<std::uint8_t, 4> const fb1 = {{0xA0, 0x02, 0x80, 0x00}};
|
||||
|
||||
/** Set the "FinishAfter" time tag on a JTx */
|
||||
struct finish_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit finish_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
/** Set the "CancelAfter" time tag on a JTx */
|
||||
struct cancel_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit cancel_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
struct condition
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit condition(Slice const& cond) : value_(strHex(cond))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit condition(std::array<std::uint8_t, N> const& c)
|
||||
: condition(makeSlice(c))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCondition.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct fulfillment
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit fulfillment(Slice condition) : value_(strHex(condition))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit fulfillment(std::array<std::uint8_t, N> f)
|
||||
: fulfillment(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFulfillment.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct finish_function
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit finish_function(std::string func) : value_(func)
|
||||
{
|
||||
}
|
||||
|
||||
explicit finish_function(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit finish_function(std::array<std::uint8_t, N> const& f)
|
||||
: finish_function(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishFunction.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct data
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit data(std::string func) : value_(func)
|
||||
{
|
||||
}
|
||||
|
||||
explicit data(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfData.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct comp_allowance
|
||||
{
|
||||
private:
|
||||
std::uint32_t value_;
|
||||
|
||||
public:
|
||||
explicit comp_allowance(std::uint32_t const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfComputationAllowance.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
/* Payment Channel */
|
||||
/******************************************************************************/
|
||||
|
||||
|
||||
@@ -220,8 +220,9 @@ public:
|
||||
getList_ = [blob = blob, sig, manifest, version](int interval) {
|
||||
// Build the contents of a version 1 format UNL file
|
||||
std::stringstream l;
|
||||
l << "{\"blob\":\"" << blob << "\"" << ",\"signature\":\"" << sig
|
||||
<< "\"" << ",\"manifest\":\"" << manifest << "\""
|
||||
l << "{\"blob\":\"" << blob << "\""
|
||||
<< ",\"signature\":\"" << sig << "\""
|
||||
<< ",\"manifest\":\"" << manifest << "\""
|
||||
<< ",\"refresh_interval\": " << interval
|
||||
<< ",\"version\":" << version << '}';
|
||||
return l.str();
|
||||
@@ -256,14 +257,15 @@ public:
|
||||
std::stringstream l;
|
||||
for (auto const& info : blobInfo)
|
||||
{
|
||||
l << "{\"blob\":\"" << info.blob << "\"" << ",\"signature\":\""
|
||||
<< info.signature << "\"},";
|
||||
l << "{\"blob\":\"" << info.blob << "\""
|
||||
<< ",\"signature\":\"" << info.signature << "\"},";
|
||||
}
|
||||
std::string blobs = l.str();
|
||||
blobs.pop_back();
|
||||
l.str(std::string());
|
||||
l << "{\"blobs_v2\": [ " << blobs << "],\"manifest\":\"" << manifest
|
||||
<< "\"" << ",\"refresh_interval\": " << interval
|
||||
<< "\""
|
||||
<< ",\"refresh_interval\": " << interval
|
||||
<< ",\"version\":" << (version + 1) << '}';
|
||||
return l.str();
|
||||
};
|
||||
|
||||
@@ -19,38 +19,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
// TODO: consider moving these to separate files (and figure out the build)
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <string>
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
extern std::string const ledgerSqnHex;
|
||||
/** Set the domain on a JTx. */
|
||||
class domain
|
||||
{
|
||||
private:
|
||||
uint256 v_;
|
||||
|
||||
extern std::string const allHostFunctionsHex;
|
||||
public:
|
||||
explicit domain(uint256 const& v) : v_(v)
|
||||
{
|
||||
}
|
||||
|
||||
extern std::string const deepRecursionHex;
|
||||
void
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
extern std::string const tx_js;
|
||||
|
||||
extern std::string const lo_js;
|
||||
|
||||
extern std::string const lo_js2;
|
||||
|
||||
extern std::string const fib32Hex;
|
||||
|
||||
extern std::string const fib64Hex;
|
||||
|
||||
extern std::string const b58Hex;
|
||||
|
||||
extern std::string const sha512PureHex;
|
||||
|
||||
extern std::string const checkJsonHex;
|
||||
|
||||
extern std::string const compareJsonHex;
|
||||
|
||||
extern std::string const zkProofHex;
|
||||
|
||||
extern std::string const sp1_wasm;
|
||||
|
||||
extern std::string const xrplStdExampleHex;
|
||||
|
||||
extern std::string const hostFunctions2Hex;
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,185 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_JTX_ESCROW_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_ESCROW_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/owners.h>
|
||||
#include <test/jtx/rate.h>
|
||||
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Escrow operations. */
|
||||
namespace escrow {
|
||||
|
||||
Json::Value
|
||||
create(AccountID const& account, AccountID const& to, STAmount const& amount);
|
||||
|
||||
inline Json::Value
|
||||
create(Account const& account, Account const& to, STAmount const& amount)
|
||||
{
|
||||
return create(account.id(), to.id(), amount);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
finish(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return finish(account.id(), from.id(), seq);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq);
|
||||
|
||||
inline Json::Value
|
||||
cancel(Account const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
return cancel(account.id(), from, seq);
|
||||
}
|
||||
|
||||
Rate
|
||||
rate(Env& env, Account const& account, std::uint32_t const& seq);
|
||||
|
||||
// A PreimageSha256 fulfillments and its associated condition.
|
||||
std::array<std::uint8_t, 4> const fb1 = {{0xA0, 0x02, 0x80, 0x00}};
|
||||
|
||||
std::array<std::uint8_t, 39> const cb1 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC,
|
||||
0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
|
||||
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95,
|
||||
0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55, 0x81, 0x01, 0x00}};
|
||||
|
||||
// Another PreimageSha256 fulfillments and its associated condition.
|
||||
std::array<std::uint8_t, 7> const fb2 = {
|
||||
{0xA0, 0x05, 0x80, 0x03, 0x61, 0x61, 0x61}};
|
||||
|
||||
std::array<std::uint8_t, 39> const cb2 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0x98, 0x34, 0x87, 0x6D, 0xCF, 0xB0,
|
||||
0x5C, 0xB1, 0x67, 0xA5, 0xC2, 0x49, 0x53, 0xEB, 0xA5, 0x8C,
|
||||
0x4A, 0xC8, 0x9B, 0x1A, 0xDF, 0x57, 0xF2, 0x8F, 0x2F, 0x9D,
|
||||
0x09, 0xAF, 0x10, 0x7E, 0xE8, 0xF0, 0x81, 0x01, 0x03}};
|
||||
|
||||
// Another PreimageSha256 fulfillment and its associated condition.
|
||||
std::array<std::uint8_t, 8> const fb3 = {
|
||||
{0xA0, 0x06, 0x80, 0x04, 0x6E, 0x69, 0x6B, 0x62}};
|
||||
|
||||
std::array<std::uint8_t, 39> const cb3 = {
|
||||
{0xA0, 0x25, 0x80, 0x20, 0x6E, 0x4C, 0x71, 0x45, 0x30, 0xC0,
|
||||
0xA4, 0x26, 0x8B, 0x3F, 0xA6, 0x3B, 0x1B, 0x60, 0x6F, 0x2D,
|
||||
0x26, 0x4A, 0x2D, 0x85, 0x7B, 0xE8, 0xA0, 0x9C, 0x1D, 0xFD,
|
||||
0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}};
|
||||
|
||||
/** Set the "FinishAfter" time tag on a JTx */
|
||||
struct finish_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit finish_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
/** Set the "CancelAfter" time tag on a JTx */
|
||||
struct cancel_time
|
||||
{
|
||||
private:
|
||||
NetClock::time_point value_;
|
||||
|
||||
public:
|
||||
explicit cancel_time(NetClock::time_point const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
struct condition
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit condition(Slice const& cond) : value_(strHex(cond))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit condition(std::array<std::uint8_t, N> const& c)
|
||||
: condition(makeSlice(c))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfCondition.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct fulfillment
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit fulfillment(Slice condition) : value_(strHex(condition))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit fulfillment(std::array<std::uint8_t, N> f)
|
||||
: fulfillment(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFulfillment.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -96,6 +96,9 @@ private:
|
||||
case asfDisallowIncomingTrustline:
|
||||
mask_ |= lsfDisallowIncomingTrustline;
|
||||
break;
|
||||
case asfAllowTrustLineLocking:
|
||||
mask_ |= lsfAllowTrustLineLocking;
|
||||
break;
|
||||
default:
|
||||
Throw<std::runtime_error>("unknown flag");
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/Env.h>
|
||||
|
||||
#include <xrpld/app/misc/AMMHelpers.h>
|
||||
#include <xrpld/app/misc/AMMUtils.h>
|
||||
#include <xrpld/rpc/detail/RPCHelpers.h>
|
||||
|
||||
@@ -39,12 +40,16 @@ number(STAmount const& a)
|
||||
return a;
|
||||
}
|
||||
|
||||
static IOUAmount
|
||||
initialTokens(STAmount const& asset1, STAmount const& asset2)
|
||||
IOUAmount
|
||||
AMM::initialTokens()
|
||||
{
|
||||
auto const product = number(asset1) * number(asset2);
|
||||
return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
|
||||
: root2(-product));
|
||||
if (!env_.enabled(fixAMMv1_3))
|
||||
{
|
||||
auto const product = number(asset1_) * number(asset2_);
|
||||
return (IOUAmount)(product.mantissa() >= 0 ? root2(product)
|
||||
: root2(-product));
|
||||
}
|
||||
return getLPTokensBalance();
|
||||
}
|
||||
|
||||
AMM::AMM(
|
||||
@@ -65,7 +70,6 @@ AMM::AMM(
|
||||
, asset1_(asset1)
|
||||
, asset2_(asset2)
|
||||
, ammID_(keylet::amm(asset1_.issue(), asset2_.issue()).key)
|
||||
, initialLPTokens_(initialTokens(asset1, asset2))
|
||||
, log_(log)
|
||||
, doClose_(close)
|
||||
, lastPurchasePrice_(0)
|
||||
@@ -78,6 +82,7 @@ AMM::AMM(
|
||||
asset1_.issue().currency,
|
||||
asset2_.issue().currency,
|
||||
ammAccount_))
|
||||
, initialLPTokens_(initialTokens())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <test/jtx/AMM.h>
|
||||
#include <test/jtx/AMMTest.h>
|
||||
#include <test/jtx/CaptureLogs.h>
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/pay.h>
|
||||
|
||||
@@ -105,15 +106,31 @@ AMMTestBase::testAMM(
|
||||
std::uint16_t tfee,
|
||||
std::optional<jtx::ter> const& ter,
|
||||
std::vector<FeatureBitset> const& vfeatures)
|
||||
{
|
||||
testAMM(
|
||||
std::move(cb),
|
||||
TestAMMArg{
|
||||
.pool = pool, .tfee = tfee, .ter = ter, .features = vfeatures});
|
||||
}
|
||||
|
||||
void
|
||||
AMMTestBase::testAMM(
|
||||
std::function<void(jtx::AMM&, jtx::Env&)>&& cb,
|
||||
TestAMMArg const& arg)
|
||||
{
|
||||
using namespace jtx;
|
||||
|
||||
for (auto const& features : vfeatures)
|
||||
std::string logs;
|
||||
|
||||
for (auto const& features : arg.features)
|
||||
{
|
||||
Env env{*this, features};
|
||||
Env env{
|
||||
*this,
|
||||
features,
|
||||
arg.noLog ? std::make_unique<CaptureLogs>(&logs) : nullptr};
|
||||
|
||||
auto const [asset1, asset2] =
|
||||
pool ? *pool : std::make_pair(XRP(10000), USD(10000));
|
||||
arg.pool ? *arg.pool : std::make_pair(XRP(10000), USD(10000));
|
||||
auto tofund = [&](STAmount const& a) -> STAmount {
|
||||
if (a.native())
|
||||
{
|
||||
@@ -143,7 +160,7 @@ AMMTestBase::testAMM(
|
||||
alice,
|
||||
asset1,
|
||||
asset2,
|
||||
CreateArg{.log = false, .tfee = tfee, .err = ter});
|
||||
CreateArg{.log = false, .tfee = arg.tfee, .err = arg.ter});
|
||||
if (BEAST_EXPECT(
|
||||
ammAlice.expectBalances(asset1, asset2, ammAlice.tokens())))
|
||||
cb(ammAlice, env);
|
||||
|
||||
@@ -199,6 +199,48 @@ Env::balance(Account const& account, Issue const& issue) const
|
||||
return {amount, lookup(issue.account).name()};
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
Env::balance(Account const& account, MPTIssue const& mptIssue) const
|
||||
{
|
||||
MPTID const id = mptIssue.getMptID();
|
||||
if (!id)
|
||||
return {STAmount(mptIssue, 0), account.name()};
|
||||
|
||||
AccountID const issuer = mptIssue.getIssuer();
|
||||
if (account.id() == issuer)
|
||||
{
|
||||
// Issuer balance
|
||||
auto const sle = le(keylet::mptIssuance(id));
|
||||
if (!sle)
|
||||
return {STAmount(mptIssue, 0), account.name()};
|
||||
|
||||
STAmount const amount{mptIssue, sle->getFieldU64(sfOutstandingAmount)};
|
||||
return {amount, lookup(issuer).name()};
|
||||
}
|
||||
else
|
||||
{
|
||||
// Holder balance
|
||||
auto const sle = le(keylet::mptoken(id, account));
|
||||
if (!sle)
|
||||
return {STAmount(mptIssue, 0), account.name()};
|
||||
|
||||
STAmount const amount{mptIssue, sle->getFieldU64(sfMPTAmount)};
|
||||
return {amount, lookup(issuer).name()};
|
||||
}
|
||||
}
|
||||
|
||||
PrettyAmount
|
||||
Env::limit(Account const& account, Issue const& issue) const
|
||||
{
|
||||
auto const sle = le(keylet::line(account.id(), issue));
|
||||
if (!sle)
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
auto const aHigh = account.id() > issue.account;
|
||||
if (sle && sle->isFieldPresent(aHigh ? sfLowLimit : sfHighLimit))
|
||||
return {(*sle)[aHigh ? sfLowLimit : sfHighLimit], account.name()};
|
||||
return {STAmount(issue, 0), account.name()};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
Env::ownerCount(Account const& account) const
|
||||
{
|
||||
|
||||
@@ -211,42 +211,6 @@ expectLedgerEntryRoot(
|
||||
return accountBalance(env, acct) == to_string(expectedValue.xrp());
|
||||
}
|
||||
|
||||
/* Escrow */
|
||||
/******************************************************************************/
|
||||
|
||||
Json::Value
|
||||
escrow(AccountID const& account, AccountID const& to, STAmount const& amount)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[jss::Destination] = to_string(to);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowFinish;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = to_string(from);
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
/* Payment Channel */
|
||||
/******************************************************************************/
|
||||
Json::Value
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/domain.h>
|
||||
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
void
|
||||
domain::operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt[sfDomainID.jsonName] = to_string(v_);
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -33,14 +33,9 @@ setupConfigForUnitTests(Config& cfg)
|
||||
using namespace jtx;
|
||||
// Default fees to old values, so tests don't have to worry about changes in
|
||||
// Config.h
|
||||
// NOTE: For new `FEES` fields, you need to wait for the first flag ledger
|
||||
// to close for the values to be activated.
|
||||
cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE;
|
||||
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
|
||||
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
|
||||
cfg.FEES.extension_compute_limit = 1'000'000;
|
||||
cfg.FEES.extension_size_limit = 100'000;
|
||||
cfg.FEES.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops
|
||||
|
||||
// The Beta API (currently v2) is always available to tests
|
||||
cfg.BETA_RPC_API = true;
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2019 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/escrow.h>
|
||||
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
/** Escrow operations. */
|
||||
namespace escrow {
|
||||
|
||||
Json::Value
|
||||
create(AccountID const& account, AccountID const& to, STAmount const& amount)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCreate;
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[jss::Destination] = to_string(to);
|
||||
jv[jss::Amount] = amount.getJson(JsonOptions::none);
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
finish(AccountID const& account, AccountID const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowFinish;
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = to_string(from);
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
cancel(AccountID const& account, Account const& from, std::uint32_t seq)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::EscrowCancel;
|
||||
jv[jss::Flags] = tfFullyCanonicalSig;
|
||||
jv[jss::Account] = to_string(account);
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
Rate
|
||||
rate(Env& env, Account const& account, std::uint32_t const& seq)
|
||||
{
|
||||
auto const sle = env.le(keylet::escrow(account.id(), seq));
|
||||
if (sle->isFieldPresent(sfTransferRate))
|
||||
return ripple::Rate((*sle)[sfTransferRate]);
|
||||
return Rate{0};
|
||||
}
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
@@ -34,6 +36,18 @@ paths::operator()(Env& env, JTx& jt) const
|
||||
auto const from = env.lookup(jv[jss::Account].asString());
|
||||
auto const to = env.lookup(jv[jss::Destination].asString());
|
||||
auto const amount = amountFromJson(sfAmount, jv[jss::Amount]);
|
||||
|
||||
std::optional<uint256> domain;
|
||||
if (jv.isMember(sfDomainID.jsonName))
|
||||
{
|
||||
if (!jv[sfDomainID.jsonName].isString())
|
||||
return;
|
||||
uint256 num;
|
||||
auto const s = jv[sfDomainID.jsonName].asString();
|
||||
if (num.parseHex(s))
|
||||
domain = num;
|
||||
}
|
||||
|
||||
Pathfinder pf(
|
||||
std::make_shared<RippleLineCache>(
|
||||
env.current(), env.app().journal("RippleLineCache")),
|
||||
@@ -43,6 +57,7 @@ paths::operator()(Env& env, JTx& jt) const
|
||||
in_.account,
|
||||
amount,
|
||||
std::nullopt,
|
||||
domain,
|
||||
env.app());
|
||||
if (!pf.findPaths(depth_))
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/permissioned_dex.h>
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
uint256
|
||||
setupDomain(
|
||||
jtx::Env& env,
|
||||
std::vector<jtx::Account> const& accounts,
|
||||
jtx::Account const& domainOwner,
|
||||
std::string const& credType)
|
||||
{
|
||||
using namespace jtx;
|
||||
env.fund(XRP(100000), domainOwner);
|
||||
env.close();
|
||||
|
||||
pdomain::Credentials credentials{{domainOwner, credType}};
|
||||
env(pdomain::setTx(domainOwner, credentials));
|
||||
|
||||
auto const objects = pdomain::getObjects(domainOwner, env);
|
||||
auto const domainID = objects.begin()->first;
|
||||
|
||||
for (auto const& account : accounts)
|
||||
{
|
||||
env(credentials::create(account, domainOwner, credType));
|
||||
env.close();
|
||||
env(credentials::accept(account, domainOwner, credType));
|
||||
env.close();
|
||||
}
|
||||
return domainID;
|
||||
}
|
||||
|
||||
PermissionedDEX::PermissionedDEX(Env& env)
|
||||
: gw("permdex-gateway")
|
||||
, domainOwner("permdex-domainOwner")
|
||||
, alice("permdex-alice")
|
||||
, bob("permdex-bob")
|
||||
, carol("permdex-carol")
|
||||
, USD(gw["USD"])
|
||||
, credType("permdex-abcde")
|
||||
{
|
||||
// Fund accounts
|
||||
env.fund(XRP(100000), alice, bob, carol, gw);
|
||||
env.close();
|
||||
|
||||
domainID = setupDomain(env, {alice, bob, carol, gw}, domainOwner, credType);
|
||||
|
||||
for (auto const& account : {alice, bob, carol, domainOwner})
|
||||
{
|
||||
env.trust(USD(1000), account);
|
||||
env.close();
|
||||
|
||||
env(pay(gw, account, USD(100)));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,51 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <test/jtx.h>
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
uint256
|
||||
setupDomain(
|
||||
jtx::Env& env,
|
||||
std::vector<jtx::Account> const& accounts,
|
||||
jtx::Account const& domainOwner = jtx::Account("domainOwner"),
|
||||
std::string const& credType = "Cred");
|
||||
|
||||
class PermissionedDEX
|
||||
{
|
||||
public:
|
||||
Account gw;
|
||||
Account domainOwner;
|
||||
Account alice;
|
||||
Account bob;
|
||||
Account carol;
|
||||
IOU USD;
|
||||
uint256 domainID;
|
||||
std::string credType;
|
||||
|
||||
PermissionedDEX(Env& env);
|
||||
};
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
@@ -37,7 +37,7 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
|
||||
{
|
||||
Book book(xrpIssue(), USD.issue());
|
||||
Book book(xrpIssue(), USD.issue(), std::nullopt);
|
||||
{
|
||||
auto d = BookDirs(*env.current(), book);
|
||||
BEAST_EXPECT(std::begin(d) == std::end(d));
|
||||
@@ -53,14 +53,16 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
env(offer("alice", Account("alice")["USD"](50), XRP(10)));
|
||||
auto d = BookDirs(
|
||||
*env.current(),
|
||||
Book(Account("alice")["USD"].issue(), xrpIssue()));
|
||||
Book(
|
||||
Account("alice")["USD"].issue(), xrpIssue(), std::nullopt));
|
||||
BEAST_EXPECT(std::distance(d.begin(), d.end()) == 1);
|
||||
}
|
||||
|
||||
{
|
||||
env(offer("alice", gw["CNY"](50), XRP(10)));
|
||||
auto d =
|
||||
BookDirs(*env.current(), Book(gw["CNY"].issue(), xrpIssue()));
|
||||
auto d = BookDirs(
|
||||
*env.current(),
|
||||
Book(gw["CNY"].issue(), xrpIssue(), std::nullopt));
|
||||
BEAST_EXPECT(std::distance(d.begin(), d.end()) == 1);
|
||||
}
|
||||
|
||||
@@ -70,7 +72,7 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
env(offer("alice", USD(50), Account("bob")["CNY"](10)));
|
||||
auto d = BookDirs(
|
||||
*env.current(),
|
||||
Book(USD.issue(), Account("bob")["CNY"].issue()));
|
||||
Book(USD.issue(), Account("bob")["CNY"].issue(), std::nullopt));
|
||||
BEAST_EXPECT(std::distance(d.begin(), d.end()) == 1);
|
||||
}
|
||||
|
||||
@@ -80,7 +82,8 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
for (auto k = 0; k < 80; ++k)
|
||||
env(offer("alice", AUD(i), XRP(j)));
|
||||
|
||||
auto d = BookDirs(*env.current(), Book(AUD.issue(), xrpIssue()));
|
||||
auto d = BookDirs(
|
||||
*env.current(), Book(AUD.issue(), xrpIssue(), std::nullopt));
|
||||
BEAST_EXPECT(std::distance(d.begin(), d.end()) == 240);
|
||||
auto i = 1, j = 3, k = 0;
|
||||
for (auto const& e : d)
|
||||
@@ -101,7 +104,8 @@ struct BookDirs_test : public beast::unit_test::suite
|
||||
{
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
test_bookdir(sa - featureFlowCross);
|
||||
test_bookdir(sa - featureFlowCross - featurePermissionedDEX);
|
||||
test_bookdir(sa - featurePermissionedDEX);
|
||||
test_bookdir(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,7 +132,8 @@ struct Directory_test : public beast::unit_test::suite
|
||||
|
||||
// Now check the orderbook: it should be in the order we placed
|
||||
// the offers.
|
||||
auto book = BookDirs(*env.current(), Book({xrpIssue(), USD.issue()}));
|
||||
auto book = BookDirs(
|
||||
*env.current(), Book({xrpIssue(), USD.issue(), std::nullopt}));
|
||||
int count = 1;
|
||||
|
||||
for (auto const& offer : book)
|
||||
@@ -291,7 +292,8 @@ struct Directory_test : public beast::unit_test::suite
|
||||
// should have no entries and be empty:
|
||||
{
|
||||
Sandbox sb(env.closed().get(), tapNONE);
|
||||
uint256 const bookBase = getBookBase({xrpIssue(), USD.issue()});
|
||||
uint256 const bookBase =
|
||||
getBookBase({xrpIssue(), USD.issue(), std::nullopt});
|
||||
|
||||
BEAST_EXPECT(dirIsEmpty(sb, keylet::page(bookBase)));
|
||||
BEAST_EXPECT(!sb.succ(bookBase, getQualityNext(bookBase)));
|
||||
|
||||
@@ -731,21 +731,6 @@ class Invariants_test : public beast::unit_test::suite
|
||||
using namespace test::jtx;
|
||||
testcase << "no zero escrow";
|
||||
|
||||
doInvariantCheck(
|
||||
{{"Cannot return non-native STAmount as XRPAmount"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
// escrow with nonnative amount
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto sleNew = std::make_shared<SLE>(
|
||||
keylet::escrow(A1, (*sle)[sfSequence] + 2));
|
||||
STAmount nonNative(A2["USD"](51));
|
||||
sleNew->setFieldAmount(sfAmount, nonNative);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"XRP net change of -1000000 doesn't match fee 0"},
|
||||
{"escrow specifies invalid amount"}},
|
||||
@@ -777,6 +762,153 @@ class Invariants_test : public beast::unit_test::suite
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// IOU < 0
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// escrow with too-little iou
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto sleNew = std::make_shared<SLE>(
|
||||
keylet::escrow(A1, (*sle)[sfSequence] + 2));
|
||||
|
||||
Issue const usd{
|
||||
Currency(0x5553440000000000), AccountID(0x4985601)};
|
||||
STAmount amt(usd, -1);
|
||||
sleNew->setFieldAmount(sfAmount, amt);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// IOU bad currency
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// escrow with bad iou currency
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto sleNew = std::make_shared<SLE>(
|
||||
keylet::escrow(A1, (*sle)[sfSequence] + 2));
|
||||
|
||||
Issue const bad{badCurrency(), AccountID(0x4985601)};
|
||||
STAmount amt(bad, 1);
|
||||
sleNew->setFieldAmount(sfAmount, amt);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// MPT < 0
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// escrow with too-little mpt
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
auto sleNew = std::make_shared<SLE>(
|
||||
keylet::escrow(A1, (*sle)[sfSequence] + 2));
|
||||
|
||||
MPTIssue const mpt{
|
||||
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
STAmount amt(mpt, -1);
|
||||
sleNew->setFieldAmount(sfAmount, amt);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// MPT OutstandingAmount < 0
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// mpissuance outstanding is negative
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
MPTIssue const mpt{
|
||||
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
|
||||
sleNew->setFieldU64(sfOutstandingAmount, -1);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// MPT LockedAmount < 0
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// mpissuance locked is less than locked
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
MPTIssue const mpt{
|
||||
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
|
||||
sleNew->setFieldU64(sfLockedAmount, -1);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// MPT OutstandingAmount < LockedAmount
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// mpissuance outstanding is less than locked
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
MPTIssue const mpt{
|
||||
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::mptIssuance(mpt.getMptID()));
|
||||
sleNew->setFieldU64(sfOutstandingAmount, 1);
|
||||
sleNew->setFieldU64(sfLockedAmount, 10);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// MPT MPTAmount < 0
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// mptoken amount is negative
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
MPTIssue const mpt{
|
||||
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
|
||||
sleNew->setFieldU64(sfMPTAmount, -1);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
// MPT LockedAmount < 0
|
||||
doInvariantCheck(
|
||||
{{"escrow specifies invalid amount"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
// mptoken locked amount is negative
|
||||
auto const sle = ac.view().peek(keylet::account(A1.id()));
|
||||
if (!sle)
|
||||
return false;
|
||||
|
||||
MPTIssue const mpt{
|
||||
MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
auto sleNew =
|
||||
std::make_shared<SLE>(keylet::mptoken(mpt.getMptID(), A1));
|
||||
sleNew->setFieldU64(sfLockedAmount, -1);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1046,6 +1178,30 @@ class Invariants_test : public beast::unit_test::suite
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
createPermissionedDomain(
|
||||
ApplyContext& ac,
|
||||
std::shared_ptr<SLE>& sle,
|
||||
test::jtx::Account const& A1,
|
||||
test::jtx::Account const& A2)
|
||||
{
|
||||
sle->setAccountID(sfOwner, A1);
|
||||
sle->setFieldU32(sfSequence, 10);
|
||||
|
||||
STArray credentials(sfAcceptedCredentials, 2);
|
||||
for (std::size_t n = 0; n < 2; ++n)
|
||||
{
|
||||
auto cred = STObject::makeInnerObject(sfCredential);
|
||||
cred.setAccountID(sfIssuer, A2);
|
||||
auto credType = "cred_type" + std::to_string(n);
|
||||
cred.setFieldVL(
|
||||
sfCredentialType, Slice(credType.c_str(), credType.size()));
|
||||
credentials.push_back(std::move(cred));
|
||||
}
|
||||
sle->setFieldArray(sfAcceptedCredentials, credentials);
|
||||
ac.view().insert(sle);
|
||||
};
|
||||
|
||||
void
|
||||
testPermissionedDomainInvariants()
|
||||
{
|
||||
@@ -1153,36 +1309,15 @@ class Invariants_test : public beast::unit_test::suite
|
||||
STTx{ttPERMISSIONED_DOMAIN_SET, [](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
auto const createPD = [](ApplyContext& ac,
|
||||
std::shared_ptr<SLE>& sle,
|
||||
Account const& A1,
|
||||
Account const& A2) {
|
||||
sle->setAccountID(sfOwner, A1);
|
||||
sle->setFieldU32(sfSequence, 10);
|
||||
|
||||
STArray credentials(sfAcceptedCredentials, 2);
|
||||
for (std::size_t n = 0; n < 2; ++n)
|
||||
{
|
||||
auto cred = STObject::makeInnerObject(sfCredential);
|
||||
cred.setAccountID(sfIssuer, A2);
|
||||
auto credType = "cred_type" + std::to_string(n);
|
||||
cred.setFieldVL(
|
||||
sfCredentialType, Slice(credType.c_str(), credType.size()));
|
||||
credentials.push_back(std::move(cred));
|
||||
}
|
||||
sle->setFieldArray(sfAcceptedCredentials, credentials);
|
||||
ac.view().insert(sle);
|
||||
};
|
||||
|
||||
testcase << "PermissionedDomain Set 1";
|
||||
doInvariantCheck(
|
||||
{{"permissioned domain with no rules."}},
|
||||
[createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
|
||||
// create PD
|
||||
createPD(ac, slePd, A1, A2);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
// update PD with empty rules
|
||||
{
|
||||
@@ -1201,12 +1336,12 @@ class Invariants_test : public beast::unit_test::suite
|
||||
doInvariantCheck(
|
||||
{{"permissioned domain bad credentials size " +
|
||||
std::to_string(tooBig)}},
|
||||
[createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
|
||||
// create PD
|
||||
createPD(ac, slePd, A1, A2);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
// update PD
|
||||
{
|
||||
@@ -1236,12 +1371,12 @@ class Invariants_test : public beast::unit_test::suite
|
||||
testcase << "PermissionedDomain Set 3";
|
||||
doInvariantCheck(
|
||||
{{"permissioned domain credentials aren't sorted"}},
|
||||
[createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
|
||||
// create PD
|
||||
createPD(ac, slePd, A1, A2);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
// update PD
|
||||
{
|
||||
@@ -1271,12 +1406,12 @@ class Invariants_test : public beast::unit_test::suite
|
||||
testcase << "PermissionedDomain Set 4";
|
||||
doInvariantCheck(
|
||||
{{"permissioned domain credentials aren't unique"}},
|
||||
[createPD](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
|
||||
// create PD
|
||||
createPD(ac, slePd, A1, A2);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
// update PD
|
||||
{
|
||||
@@ -1300,6 +1435,175 @@ class Invariants_test : public beast::unit_test::suite
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
void
|
||||
testPermissionedDEX()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
testcase << "PermissionedDEX";
|
||||
|
||||
doInvariantCheck(
|
||||
{{"domain doesn't exist"}},
|
||||
[](Account const& A1, Account const&, ApplyContext& ac) {
|
||||
Keylet const offerKey = keylet::offer(A1.id(), 10);
|
||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
||||
sleOffer->setAccountID(sfAccount, A1);
|
||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
||||
ac.view().insert(sleOffer);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{
|
||||
ttOFFER_CREATE,
|
||||
[](STObject& tx) {
|
||||
tx.setFieldH256(
|
||||
sfDomainID,
|
||||
uint256{
|
||||
"F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
|
||||
"70F3649CE134E5"});
|
||||
Account const A1{"A1"};
|
||||
tx.setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
tx.setFieldAmount(sfTakerGets, XRP(1));
|
||||
}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
// missing domain ID in offer object
|
||||
doInvariantCheck(
|
||||
{{"hybrid offer is malformed"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
Keylet const offerKey = keylet::offer(A2.id(), 10);
|
||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
||||
sleOffer->setAccountID(sfAccount, A2);
|
||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
||||
sleOffer->setFlag(lsfHybrid);
|
||||
|
||||
STArray bookArr;
|
||||
bookArr.push_back(STObject::makeInnerObject(sfBook));
|
||||
sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
|
||||
ac.view().insert(sleOffer);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
// more than one entry in sfAdditionalBooks
|
||||
doInvariantCheck(
|
||||
{{"hybrid offer is malformed"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
Keylet const offerKey = keylet::offer(A2.id(), 10);
|
||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
||||
sleOffer->setAccountID(sfAccount, A2);
|
||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
||||
sleOffer->setFlag(lsfHybrid);
|
||||
sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
|
||||
|
||||
STArray bookArr;
|
||||
bookArr.push_back(STObject::makeInnerObject(sfBook));
|
||||
bookArr.push_back(STObject::makeInnerObject(sfBook));
|
||||
sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
|
||||
ac.view().insert(sleOffer);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
// hybrid offer missing sfAdditionalBooks
|
||||
doInvariantCheck(
|
||||
{{"hybrid offer is malformed"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
Keylet const offerKey = keylet::offer(A2.id(), 10);
|
||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
||||
sleOffer->setAccountID(sfAccount, A2);
|
||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
||||
sleOffer->setFlag(lsfHybrid);
|
||||
sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
|
||||
ac.view().insert(sleOffer);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttOFFER_CREATE, [&](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"transaction consumed wrong domains"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
Keylet const badDomainKeylet =
|
||||
keylet::permissionedDomain(A1.id(), 20);
|
||||
auto sleBadPd = std::make_shared<SLE>(badDomainKeylet);
|
||||
createPermissionedDomain(ac, sleBadPd, A1, A2);
|
||||
|
||||
Keylet const offerKey = keylet::offer(A2.id(), 10);
|
||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
||||
sleOffer->setAccountID(sfAccount, A2);
|
||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
||||
sleOffer->setFieldH256(sfDomainID, pdKeylet.key);
|
||||
ac.view().insert(sleOffer);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{
|
||||
ttOFFER_CREATE,
|
||||
[&](STObject& tx) {
|
||||
Account const A1{"A1"};
|
||||
Keylet const badDomainKey =
|
||||
keylet::permissionedDomain(A1.id(), 20);
|
||||
tx.setFieldH256(sfDomainID, badDomainKey.key);
|
||||
tx.setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
tx.setFieldAmount(sfTakerGets, XRP(1));
|
||||
}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"domain transaction affected regular offers"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
Keylet const pdKeylet = keylet::permissionedDomain(A1.id(), 10);
|
||||
auto slePd = std::make_shared<SLE>(pdKeylet);
|
||||
createPermissionedDomain(ac, slePd, A1, A2);
|
||||
|
||||
Keylet const offerKey = keylet::offer(A2.id(), 10);
|
||||
auto sleOffer = std::make_shared<SLE>(offerKey);
|
||||
sleOffer->setAccountID(sfAccount, A2);
|
||||
sleOffer->setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
sleOffer->setFieldAmount(sfTakerGets, XRP(1));
|
||||
ac.view().insert(sleOffer);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{
|
||||
ttOFFER_CREATE,
|
||||
[&](STObject& tx) {
|
||||
Account const A1{"A1"};
|
||||
Keylet const domainKey =
|
||||
keylet::permissionedDomain(A1.id(), 10);
|
||||
tx.setFieldH256(sfDomainID, domainKey.key);
|
||||
tx.setFieldAmount(sfTakerPays, A1["USD"](10));
|
||||
tx.setFieldAmount(sfTakerGets, XRP(1));
|
||||
}},
|
||||
{tecINVARIANT_FAILED, tecINVARIANT_FAILED});
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
@@ -1318,6 +1622,7 @@ public:
|
||||
testValidNewAccountRoot();
|
||||
testNFTokenPageInvariants();
|
||||
testPermissionedDomainInvariants();
|
||||
testPermissionedDEX();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -421,7 +421,8 @@ public:
|
||||
};
|
||||
using namespace jtx;
|
||||
auto const sa = supported_amendments();
|
||||
testAll(sa - featureFlowCross);
|
||||
testAll(sa - featureFlowCross - featurePermissionedDEX);
|
||||
testAll(sa - featurePermissionedDEX);
|
||||
testAll(sa);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -897,8 +897,9 @@ protected:
|
||||
printPeers(std::string const& msg, std::uint16_t validator = 0)
|
||||
{
|
||||
auto peers = network_.overlay().getPeers(network_.validator(validator));
|
||||
std::cout << msg << " " << "num peers "
|
||||
<< (int)network_.overlay().getNumPeers() << std::endl;
|
||||
std::cout << msg << " "
|
||||
<< "num peers " << (int)network_.overlay().getNumPeers()
|
||||
<< std::endl;
|
||||
for (auto& [k, v] : peers)
|
||||
std::cout << k << ":" << (int)std::get<reduce_relay::PeerState>(v)
|
||||
<< " ";
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
#include <xrpl/protocol/Book.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <typeinfo>
|
||||
#include <unordered_set>
|
||||
@@ -46,6 +49,8 @@ namespace ripple {
|
||||
class Issue_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
using Domain = uint256;
|
||||
|
||||
// Comparison, hash tests for uint60 (via base_uint)
|
||||
template <typename Unsigned>
|
||||
void
|
||||
@@ -239,6 +244,120 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
template <class Set>
|
||||
void
|
||||
testIssueDomainSet()
|
||||
{
|
||||
Currency const c1(1);
|
||||
AccountID const i1(1);
|
||||
Currency const c2(2);
|
||||
AccountID const i2(2);
|
||||
Issue const a1(c1, i1);
|
||||
Issue const a2(c2, i2);
|
||||
uint256 const domain1{1};
|
||||
uint256 const domain2{2};
|
||||
|
||||
Set c;
|
||||
|
||||
c.insert(std::make_pair(a1, domain1));
|
||||
if (!BEAST_EXPECT(c.size() == 1))
|
||||
return;
|
||||
c.insert(std::make_pair(a2, domain1));
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
c.insert(std::make_pair(a2, domain2));
|
||||
if (!BEAST_EXPECT(c.size() == 3))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(Issue(c1, i2), domain1)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(a1, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(a2, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(a2, domain2)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
}
|
||||
|
||||
template <class Map>
|
||||
void
|
||||
testIssueDomainMap()
|
||||
{
|
||||
Currency const c1(1);
|
||||
AccountID const i1(1);
|
||||
Currency const c2(2);
|
||||
AccountID const i2(2);
|
||||
Issue const a1(c1, i1);
|
||||
Issue const a2(c2, i2);
|
||||
uint256 const domain1{1};
|
||||
uint256 const domain2{2};
|
||||
|
||||
Map c;
|
||||
|
||||
c.insert(std::make_pair(std::make_pair(a1, domain1), 1));
|
||||
if (!BEAST_EXPECT(c.size() == 1))
|
||||
return;
|
||||
c.insert(std::make_pair(std::make_pair(a2, domain1), 2));
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
c.insert(std::make_pair(std::make_pair(a2, domain2), 2));
|
||||
if (!BEAST_EXPECT(c.size() == 3))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(Issue(c1, i2), domain1)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(a1, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(a2, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(std::make_pair(a2, domain2)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
testIssueDomainSets()
|
||||
{
|
||||
testcase("std::set <std::pair<Issue, Domain>>");
|
||||
testIssueDomainSet<std::set<std::pair<Issue, Domain>>>();
|
||||
|
||||
testcase("std::set <std::pair<Issue, Domain>>");
|
||||
testIssueDomainSet<std::set<std::pair<Issue, Domain>>>();
|
||||
|
||||
testcase("hash_set <std::pair<Issue, Domain>>");
|
||||
testIssueDomainSet<hash_set<std::pair<Issue, Domain>>>();
|
||||
|
||||
testcase("hash_set <std::pair<Issue, Domain>>");
|
||||
testIssueDomainSet<hash_set<std::pair<Issue, Domain>>>();
|
||||
}
|
||||
|
||||
void
|
||||
testIssueDomainMaps()
|
||||
{
|
||||
testcase("std::map <std::pair<Issue, Domain>, int>");
|
||||
testIssueDomainMap<std::map<std::pair<Issue, Domain>, int>>();
|
||||
|
||||
testcase("std::map <std::pair<Issue, Domain>, int>");
|
||||
testIssueDomainMap<std::map<std::pair<Issue, Domain>, int>>();
|
||||
|
||||
#if RIPPLE_ASSETS_ENABLE_STD_HASH
|
||||
testcase("hash_map <std::pair<Issue, Domain>, int>");
|
||||
testIssueDomainMap<hash_map<std::pair<Issue, Domain>, int>>();
|
||||
|
||||
testcase("hash_map <std::pair<Issue, Domain>, int>");
|
||||
testIssueDomainMap<hash_map<std::pair<Issue, Domain>, int>>();
|
||||
|
||||
testcase("hardened_hash_map <std::pair<Issue, Domain>, int>");
|
||||
testIssueDomainMap<hardened_hash_map<std::pair<Issue, Domain>, int>>();
|
||||
|
||||
testcase("hardened_hash_map <std::pair<Issue, Domain>, int>");
|
||||
testIssueDomainMap<hardened_hash_map<std::pair<Issue, Domain>, int>>();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
testIssueSets()
|
||||
{
|
||||
@@ -306,15 +425,88 @@ public:
|
||||
Issue a2(c1, i2);
|
||||
Issue a3(c2, i2);
|
||||
Issue a4(c3, i2);
|
||||
uint256 const domain1{1};
|
||||
uint256 const domain2{2};
|
||||
|
||||
BEAST_EXPECT(Book(a1, a2) != Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a1, a2) < Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a1, a2) <= Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a2, a3) <= Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a2, a3) == Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a2, a3) >= Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a3, a4) >= Book(a2, a3));
|
||||
BEAST_EXPECT(Book(a3, a4) > Book(a2, a3));
|
||||
// Books without domains
|
||||
BEAST_EXPECT(Book(a1, a2, std::nullopt) != Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a1, a2, std::nullopt) < Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a1, a2, std::nullopt) <= Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) <= Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) == Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) >= Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a3, a4, std::nullopt) >= Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a3, a4, std::nullopt) > Book(a2, a3, std::nullopt));
|
||||
|
||||
// test domain books
|
||||
{
|
||||
// Books with different domains
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) != Book(a2, a3, domain2));
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) < Book(a2, a3, domain2));
|
||||
BEAST_EXPECT(Book(a2, a3, domain2) > Book(a2, a3, domain1));
|
||||
|
||||
// One Book has a domain, the other does not
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) != Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) < Book(a2, a3, domain1));
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) > Book(a2, a3, std::nullopt));
|
||||
|
||||
// Both Books have the same domain
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) == Book(a2, a3, domain1));
|
||||
BEAST_EXPECT(Book(a2, a3, domain2) == Book(a2, a3, domain2));
|
||||
BEAST_EXPECT(
|
||||
Book(a2, a3, std::nullopt) == Book(a2, a3, std::nullopt));
|
||||
|
||||
// Both Books have no domain
|
||||
BEAST_EXPECT(
|
||||
Book(a2, a3, std::nullopt) == Book(a2, a3, std::nullopt));
|
||||
|
||||
// Testing comparisons with >= and <=
|
||||
|
||||
// When comparing books with domain1 vs domain2
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) <= Book(a2, a3, domain2));
|
||||
BEAST_EXPECT(Book(a2, a3, domain2) >= Book(a2, a3, domain1));
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) >= Book(a2, a3, domain1));
|
||||
BEAST_EXPECT(Book(a2, a3, domain2) <= Book(a2, a3, domain2));
|
||||
|
||||
// One Book has domain1 and the other has no domain
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) > Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) < Book(a2, a3, domain1));
|
||||
|
||||
// One Book has domain2 and the other has no domain
|
||||
BEAST_EXPECT(Book(a2, a3, domain2) > Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) < Book(a2, a3, domain2));
|
||||
|
||||
// Comparing two Books with no domains
|
||||
BEAST_EXPECT(
|
||||
Book(a2, a3, std::nullopt) <= Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(
|
||||
Book(a2, a3, std::nullopt) >= Book(a2, a3, std::nullopt));
|
||||
|
||||
// Test case where domain1 is less than domain2
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) <= Book(a2, a3, domain2));
|
||||
BEAST_EXPECT(Book(a2, a3, domain2) >= Book(a2, a3, domain1));
|
||||
|
||||
// Test case where domain2 is equal to domain1
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) >= Book(a2, a3, domain1));
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) <= Book(a2, a3, domain1));
|
||||
|
||||
// More test cases involving a4 (with domain2)
|
||||
|
||||
// Comparing Book with domain2 (a4) to a Book with domain1
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) < Book(a3, a4, domain2));
|
||||
BEAST_EXPECT(Book(a3, a4, domain2) > Book(a2, a3, domain1));
|
||||
|
||||
// Comparing Book with domain2 (a4) to a Book with no domain
|
||||
BEAST_EXPECT(Book(a3, a4, domain2) > Book(a2, a3, std::nullopt));
|
||||
BEAST_EXPECT(Book(a2, a3, std::nullopt) < Book(a3, a4, domain2));
|
||||
|
||||
// Comparing Book with domain2 (a4) to a Book with the same domain
|
||||
BEAST_EXPECT(Book(a3, a4, domain2) == Book(a3, a4, domain2));
|
||||
|
||||
// Comparing Book with domain2 (a4) to a Book with domain1
|
||||
BEAST_EXPECT(Book(a2, a3, domain1) < Book(a3, a4, domain2));
|
||||
BEAST_EXPECT(Book(a3, a4, domain2) > Book(a2, a3, domain1));
|
||||
}
|
||||
|
||||
std::hash<Book> hash;
|
||||
|
||||
@@ -336,18 +528,99 @@ public:
|
||||
// log << std::hex << hash (Book (a3, a4));
|
||||
// log << std::hex << hash (Book (a3, a4));
|
||||
|
||||
BEAST_EXPECT(hash(Book(a1, a2)) == hash(Book(a1, a2)));
|
||||
BEAST_EXPECT(hash(Book(a1, a3)) == hash(Book(a1, a3)));
|
||||
BEAST_EXPECT(hash(Book(a1, a4)) == hash(Book(a1, a4)));
|
||||
BEAST_EXPECT(hash(Book(a2, a3)) == hash(Book(a2, a3)));
|
||||
BEAST_EXPECT(hash(Book(a2, a4)) == hash(Book(a2, a4)));
|
||||
BEAST_EXPECT(hash(Book(a3, a4)) == hash(Book(a3, a4)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) ==
|
||||
hash(Book(a1, a2, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a3, std::nullopt)) ==
|
||||
hash(Book(a1, a3, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a4, std::nullopt)) ==
|
||||
hash(Book(a1, a4, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a3, std::nullopt)) ==
|
||||
hash(Book(a2, a3, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a4, std::nullopt)) ==
|
||||
hash(Book(a2, a4, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a3, a4, std::nullopt)) ==
|
||||
hash(Book(a3, a4, std::nullopt)));
|
||||
|
||||
BEAST_EXPECT(hash(Book(a1, a2)) != hash(Book(a1, a3)));
|
||||
BEAST_EXPECT(hash(Book(a1, a2)) != hash(Book(a1, a4)));
|
||||
BEAST_EXPECT(hash(Book(a1, a2)) != hash(Book(a2, a3)));
|
||||
BEAST_EXPECT(hash(Book(a1, a2)) != hash(Book(a2, a4)));
|
||||
BEAST_EXPECT(hash(Book(a1, a2)) != hash(Book(a3, a4)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) !=
|
||||
hash(Book(a1, a3, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) !=
|
||||
hash(Book(a1, a4, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) !=
|
||||
hash(Book(a2, a3, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) !=
|
||||
hash(Book(a2, a4, std::nullopt)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) !=
|
||||
hash(Book(a3, a4, std::nullopt)));
|
||||
|
||||
// Books with domain
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, domain1)) == hash(Book(a1, a2, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a3, domain1)) == hash(Book(a1, a3, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a4, domain1)) == hash(Book(a1, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a3, domain1)) == hash(Book(a2, a3, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a4, domain1)) == hash(Book(a2, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a3, a4, domain1)) == hash(Book(a3, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) ==
|
||||
hash(Book(a1, a2, std::nullopt)));
|
||||
|
||||
// Comparing Books with domain1 vs no domain
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, std::nullopt)) != hash(Book(a1, a2, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a3, std::nullopt)) != hash(Book(a1, a3, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a4, std::nullopt)) != hash(Book(a1, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a3, std::nullopt)) != hash(Book(a2, a3, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a4, std::nullopt)) != hash(Book(a2, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a3, a4, std::nullopt)) != hash(Book(a3, a4, domain1)));
|
||||
|
||||
// Books with domain1 but different Issues
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, domain1)) != hash(Book(a1, a3, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, domain1)) != hash(Book(a1, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a3, domain1)) != hash(Book(a2, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, domain1)) != hash(Book(a2, a3, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a4, domain1)) != hash(Book(a3, a4, domain1)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a3, a4, domain1)) != hash(Book(a1, a4, domain1)));
|
||||
|
||||
// Books with domain1 and domain2
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a2, domain1)) != hash(Book(a1, a2, domain2)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a3, domain1)) != hash(Book(a1, a3, domain2)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a1, a4, domain1)) != hash(Book(a1, a4, domain2)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a3, domain1)) != hash(Book(a2, a3, domain2)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a2, a4, domain1)) != hash(Book(a2, a4, domain2)));
|
||||
BEAST_EXPECT(
|
||||
hash(Book(a3, a4, domain1)) != hash(Book(a3, a4, domain2)));
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -362,8 +635,16 @@ public:
|
||||
AccountID const i2(2);
|
||||
Issue const a1(c1, i1);
|
||||
Issue const a2(c2, i2);
|
||||
Book const b1(a1, a2);
|
||||
Book const b2(a2, a1);
|
||||
Book const b1(a1, a2, std::nullopt);
|
||||
Book const b2(a2, a1, std::nullopt);
|
||||
|
||||
uint256 const domain1{1};
|
||||
uint256 const domain2{2};
|
||||
|
||||
Book const b1_d1(a1, a2, domain1);
|
||||
Book const b2_d1(a2, a1, domain1);
|
||||
Book const b1_d2(a1, a2, domain2);
|
||||
Book const b2_d2(a2, a1, domain2);
|
||||
|
||||
{
|
||||
Set c;
|
||||
@@ -375,11 +656,11 @@ public:
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1)) == 0))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1, std::nullopt)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
@@ -395,11 +676,11 @@ public:
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1)) == 0))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1, std::nullopt)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
@@ -413,6 +694,66 @@ public:
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
{
|
||||
Set c;
|
||||
|
||||
c.insert(b1_d1);
|
||||
if (!BEAST_EXPECT(c.size() == 1))
|
||||
return;
|
||||
c.insert(b2_d1);
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
c.insert(b1_d2);
|
||||
if (!BEAST_EXPECT(c.size() == 3))
|
||||
return;
|
||||
c.insert(b2_d2);
|
||||
if (!BEAST_EXPECT(c.size() == 4))
|
||||
return;
|
||||
|
||||
// Try removing non-existent elements
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a2, domain1)) == 0))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, domain2)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, domain2)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Set c;
|
||||
|
||||
c.insert(b1);
|
||||
c.insert(b2);
|
||||
c.insert(b1_d1);
|
||||
c.insert(b2_d1);
|
||||
if (!BEAST_EXPECT(c.size() == 4))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Map>
|
||||
@@ -425,8 +766,16 @@ public:
|
||||
AccountID const i2(2);
|
||||
Issue const a1(c1, i1);
|
||||
Issue const a2(c2, i2);
|
||||
Book const b1(a1, a2);
|
||||
Book const b2(a2, a1);
|
||||
Book const b1(a1, a2, std::nullopt);
|
||||
Book const b2(a2, a1, std::nullopt);
|
||||
|
||||
uint256 const domain1{1};
|
||||
uint256 const domain2{2};
|
||||
|
||||
Book const b1_d1(a1, a2, domain1);
|
||||
Book const b2_d1(a2, a1, domain1);
|
||||
Book const b1_d2(a1, a2, domain2);
|
||||
Book const b2_d2(a2, a1, domain2);
|
||||
|
||||
// typename Map::value_type value_type;
|
||||
// std::pair <Book const, int> value_type;
|
||||
@@ -443,11 +792,11 @@ public:
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1)) == 0))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1, std::nullopt)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
@@ -465,11 +814,77 @@ public:
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1)) == 0))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1, std::nullopt)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1)) == 1))
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Map c;
|
||||
|
||||
c.insert(std::make_pair(b1_d1, 10));
|
||||
if (!BEAST_EXPECT(c.size() == 1))
|
||||
return;
|
||||
c.insert(std::make_pair(b2_d1, 20));
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
c.insert(std::make_pair(b1_d2, 30));
|
||||
if (!BEAST_EXPECT(c.size() == 3))
|
||||
return;
|
||||
c.insert(std::make_pair(b2_d2, 40));
|
||||
if (!BEAST_EXPECT(c.size() == 4))
|
||||
return;
|
||||
|
||||
// Try removing non-existent elements
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a2, domain1)) == 0))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, domain2)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, domain2)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Map c;
|
||||
|
||||
c.insert(std::make_pair(b1, 1));
|
||||
c.insert(std::make_pair(b2, 2));
|
||||
c.insert(std::make_pair(b1_d1, 3));
|
||||
c.insert(std::make_pair(b2_d1, 4));
|
||||
if (!BEAST_EXPECT(c.size() == 4))
|
||||
return;
|
||||
|
||||
// Try removing non-existent elements
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a1, domain1)) == 0))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a2, domain2)) == 0))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, std::nullopt)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.size() == 2))
|
||||
return;
|
||||
|
||||
if (!BEAST_EXPECT(c.erase(Book(a1, a2, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.erase(Book(a2, a1, domain1)) == 1))
|
||||
return;
|
||||
if (!BEAST_EXPECT(c.empty()))
|
||||
return;
|
||||
@@ -556,6 +971,10 @@ public:
|
||||
|
||||
testBookSets();
|
||||
testBookMaps();
|
||||
|
||||
// ---
|
||||
testIssueDomainSets();
|
||||
testIssueDomainMaps();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpl/basics/random.h>
|
||||
#include <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
@@ -668,6 +670,366 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanAddXRP()
|
||||
{
|
||||
testcase("can add xrp");
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(0));
|
||||
STAmount amt2(XRPAmount(1000));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(0));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two positive XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(500));
|
||||
STAmount amt2(XRPAmount(1500));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two negative XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(-500));
|
||||
STAmount amt2(XRPAmount(-1500));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding a positive and a negative XRP amount
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(-1000));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Overflow check for max XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
STAmount amt2(XRPAmount(1));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for min XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
amt1 += XRPAmount(1);
|
||||
STAmount amt2(XRPAmount(-1));
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanAddIOU()
|
||||
{
|
||||
testcase("can add iou");
|
||||
|
||||
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
|
||||
Issue const eur{Currency(0x4555520000000000), AccountID(0x4985601)};
|
||||
|
||||
// Adding two IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, 500);
|
||||
STAmount amt2(usd, 1500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding a positive and a negative IOU amount
|
||||
{
|
||||
STAmount amt1(usd, 1000);
|
||||
STAmount amt2(usd, -1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Overflow check for max IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, std::numeric_limits<int64_t>::max());
|
||||
STAmount amt2(usd, 1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for min IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, std::numeric_limits<std::int64_t>::min());
|
||||
STAmount amt2(usd, -1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding XRP and IOU
|
||||
{
|
||||
STAmount amt1(XRPAmount(1));
|
||||
STAmount amt2(usd, 1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different IOU issues (non zero)
|
||||
{
|
||||
STAmount amt1(usd, 1000);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different IOU issues (zero)
|
||||
{
|
||||
STAmount amt1(usd, 0);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanAddMPT()
|
||||
{
|
||||
testcase("can add mpt");
|
||||
|
||||
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
MPTIssue const mpt2{MPTIssue{makeMptID(2, AccountID(0x4985601))}};
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(mpt, 0);
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding zero
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, 0);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two positive MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, 500);
|
||||
STAmount amt2(mpt, 1500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding two negative MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, -500);
|
||||
STAmount amt2(mpt, -1500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Adding a positive and a negative MPT amount
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, -1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Overflow check for max MPT amounts
|
||||
{
|
||||
STAmount amt1(
|
||||
mpt, std::numeric_limits<MPTAmount::value_type>::max());
|
||||
STAmount amt2(mpt, 1);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for min MPT amounts
|
||||
// Note: Cannot check min MPT overflow because you cannot initialize the
|
||||
// STAmount with a negative MPT amount.
|
||||
|
||||
// Adding MPT and XRP
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different MPT issues (non zero)
|
||||
{
|
||||
STAmount amt1(mpt2, 500);
|
||||
STAmount amt2(mpt, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Adding different MPT issues (non zero)
|
||||
{
|
||||
STAmount amt1(mpt2, 0);
|
||||
STAmount amt2(mpt, 500);
|
||||
BEAST_EXPECT(canAdd(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSubtractXRP()
|
||||
{
|
||||
testcase("can subtract xrp");
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(0));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(XRPAmount(0));
|
||||
STAmount amt2(XRPAmount(1000));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting two positive XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(1500));
|
||||
STAmount amt2(XRPAmount(500));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting two negative XRP amounts
|
||||
{
|
||||
STAmount amt1(XRPAmount(-1500));
|
||||
STAmount amt2(XRPAmount(-500));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting a positive and a negative XRP amount
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(XRPAmount(-1000));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Underflow check for min XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
amt1 += XRPAmount(1);
|
||||
STAmount amt2(XRPAmount(1));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Overflow check for max XRP amounts
|
||||
{
|
||||
STAmount amt1(std::numeric_limits<XRPAmount::value_type>::max());
|
||||
STAmount amt2(XRPAmount(-1));
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSubtractIOU()
|
||||
{
|
||||
testcase("can subtract iou");
|
||||
Issue const usd{Currency(0x5553440000000000), AccountID(0x4985601)};
|
||||
Issue const eur{Currency(0x4555520000000000), AccountID(0x4985601)};
|
||||
|
||||
// Subtracting two IOU amounts
|
||||
{
|
||||
STAmount amt1(usd, 1500);
|
||||
STAmount amt2(usd, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting XRP and IOU
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(usd, 1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different IOU issues (non zero)
|
||||
{
|
||||
STAmount amt1(usd, 1000);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different IOU issues (zero)
|
||||
{
|
||||
STAmount amt1(usd, 0);
|
||||
STAmount amt2(eur, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCanSubtractMPT()
|
||||
{
|
||||
testcase("can subtract mpt");
|
||||
|
||||
MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}};
|
||||
MPTIssue const mpt2{MPTIssue{makeMptID(2, AccountID(0x4985601))}};
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, 0);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting zero
|
||||
{
|
||||
STAmount amt1(mpt, 0);
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting two positive MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, 1500);
|
||||
STAmount amt2(mpt, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting two negative MPT amounts
|
||||
{
|
||||
STAmount amt1(mpt, -1500);
|
||||
STAmount amt2(mpt, -500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Subtracting a positive and a negative MPT amount
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt, -1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == true);
|
||||
}
|
||||
|
||||
// Underflow check for min MPT amounts
|
||||
// Note: Cannot check min MPT underflow because you cannot initialize
|
||||
// the STAmount with a negative MPT amount.
|
||||
|
||||
// Overflow check for max positive MPT amounts (should fail)
|
||||
{
|
||||
STAmount amt1(
|
||||
mpt, std::numeric_limits<MPTAmount::value_type>::max());
|
||||
STAmount amt2(mpt, -2);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting MPT and XRP
|
||||
{
|
||||
STAmount amt1(XRPAmount(1000));
|
||||
STAmount amt2(mpt, 1000);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different MPT issues (non zero)
|
||||
{
|
||||
STAmount amt1(mpt, 1000);
|
||||
STAmount amt2(mpt2, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
|
||||
// Subtracting different MPT issues (zero)
|
||||
{
|
||||
STAmount amt1(mpt, 0);
|
||||
STAmount amt2(mpt2, 500);
|
||||
BEAST_EXPECT(canSubtract(amt1, amt2) == false);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
@@ -681,6 +1043,12 @@ public:
|
||||
testRounding();
|
||||
testConvertXRP();
|
||||
testConvertIOU();
|
||||
testCanAddXRP();
|
||||
testCanAddIOU();
|
||||
testCanAddMPT();
|
||||
testCanSubtractXRP();
|
||||
testCanSubtractIOU();
|
||||
testCanSubtractMPT();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loaded 100 of 202 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user