Compare commits

..
Author SHA1 Message Date
Valentin Balaschenko 607d0f7e02 remove temp vector 2025-07-25 14:03:31 +01:00
Valentin Balaschenko 8461c7d0b6 sweep every 10 sec instead of 1 min 2025-06-18 17:00:17 +01:00
Michael Legleux edb4f0342c Set version to 2.5.0-rc2 2025-06-11 17:10:45 -07:00
yinyiqian1 ea17abb92a fix: Ensure delegate tests do not silently fail with batch (#5476)
The tests that ensure `tfInnerBatchTxn` won't block delegated transactions silently fail in `Delegate_test.cpp`. This change removes these cases from that file and adds them to `Batch_test.cpp` instead where they do not silently fail, because there the batch delegate results are explicitly checked. Moving them to that file further avoids refactoring many helper functions.
2025-06-11 13:21:24 +08:00
Mayukha Vadari 35a40a8e62 fix: Improve multi-sign usage of simulate (#5479)
This change allows users to submit simulate requests from a multi-sign account without needing to specify the accounts that are doing the multi-signing, and fixes an error with simulate that allowed double-"signed" (both single-sign and multi-sign public keys are provided) transactions.
2025-06-10 14:47:27 +08:00
Ed Hennis d494bf45b2 refactor: Collapse some split log messages into one (#5347)
Multi-line log messages are hard to work with. Writing these handful of related messages as one message should make the log a tiny bit easier to manage.
2025-06-06 16:01:02 +00:00
Vlad 8bf4a5cbff chore: Remove external project build cores division (#5475)
The CMake statements that make it seem as if the number of cores used to build external project dependencies is halved don't actually do anything. This change removes these statements.
2025-06-05 13:37:30 +00:00
Denis Angell 58c2c82a30 fix: Amendment-guard TokenEscrow preclaim and expand tests (#5473)
This change amendment-guards the preclaim for `TokenEscrow`, as well as expands tests to increase code coverage.
2025-06-05 12:54:45 +00:00
Michael Legleux 11edaa441d Set version to 2.5.0-rc1 (#5472) 2025-06-04 17:55:23 +00:00
yinyiqian1 a5e953b191 fix: Add tecNO_DELEGATE_PERMISSION and fix flags (#5465)
* Adds `tecNO_DELEGATE_PERMISSION` for unauthorized transactions sent by a delegated account.
* Returns `tecNO_TARGET` instead of `terNO_ACCOUNT` for the `DelegateSet` transaction if the delegated account does not exist.
* Fixes `tfFullyCanonicalSig` and `tfInnerBatchTxn` blocking transactions issue by adding `tfUniversal` in the permission related masks in `txFlags.h`
2025-06-03 22:20:29 +00:00
Mark Travis 506ae12a8c Increase network i/o capacity (#5464)
The change increases the default network I/O worker thread pool size from 2 to 6. This will improve stability, as worker thread saturation correlates to desyncs, particularly on high-traffic peers, such as hubs.
2025-06-03 21:33:09 +00:00
Ayaz Salikhov 0310c5cbe0 fix: Specify transitive_headers when building with Conan 2 (#5462)
To be able to consume `rippled` in Conan 2, the recipe should specify transitive_headers for external libraries that are present in the exported header files. This change remains compatibility with Conan 1, where this flag was not present.
2025-06-03 17:33:32 +00:00
Denis Angell 053e1af7ff Add support for XLS-85 Token Escrow (#5185)
- Specification: https://github.com/XRPLF/XRPL-Standards/pull/272
- Amendment: `TokenEscrow`
- Enables escrowing of IOU and MPT tokens in addition to native XRP.
- Allows accounts to lock issued tokens (IOU/MPT) in escrow objects, with support for freeze, authorization, and transfer rates.
- Adds new ledger fields (`sfLockedAmount`, `sfIssuerNode`, etc.) to track locked balances for IOU and MPT escrows.
- Updates EscrowCreate, EscrowFinish, and EscrowCancel transaction logic to support IOU and MPT assets, including proper handling of trustlines and MPT authorization, transfer rates, and locked balances.
- Enforces invariant checks for escrowed IOU/MPT amounts.
- Extends GatewayBalances RPC to report locked (escrowed) balances.
2025-06-03 12:51:55 -04:00
Vlad 7e24adbdd0 fix: Address NFT interactions with trustlines (#5297)
The changes are focused on fixing NFT transactions bypassing the trustline authorization requirement and potential invariant violation when interacting with deep frozen trustlines.
2025-06-02 16:13:20 +00:00
Gregory Tsipenyuk 621df422a7 fix: Add AMMv1_3 amendment (#5203)
* Add AMM bid/create/deposit/swap/withdraw/vote invariants:
  - Deposit, Withdrawal invariants: `sqrt(asset1Balance * asset2Balance) >= LPTokens`.
  - Bid: `sqrt(asset1Balance * asset2Balance) > LPTokens` and the pool balances don't change.
  - Create: `sqrt(asset1Balance * assetBalance2) == LPTokens`.
  - Swap: `asset1BalanceAfter * asset2BalanceAfter >= asset1BalanceBefore * asset2BalanceBefore`
     and `LPTokens` don't change.
  - Vote: `LPTokens` and pool balances don't change.
  - All AMM and swap transactions: amounts and tokens are greater than zero, except on withdrawal if all tokens
    are withdrawn.
* Add AMM deposit and withdraw rounding to ensure AMM invariant:
  - On deposit, tokens out are rounded downward and deposit amount is rounded upward.
  - On withdrawal, tokens in are rounded upward and withdrawal amount is rounded downward.
* Add Order Book Offer invariant to verify consumed amounts. Consumed amounts are less than the offer.
* Fix Bid validation. `AuthAccount` can't have duplicate accounts or the submitter account.
2025-06-02 09:52:10 -04:00
Shawn Xie 0a34b5c691 Add support for XLS-81 Permissioned DEX (#5404)
Modified transactions:
- OfferCreate
- Payment

Modified RPCs:
- book_changes
- subscribe
- book_offers
- ripple_path_find
- path_find

Spec: https://github.com/XRPLF/XRPL-Standards/pull/281
2025-05-30 13:24:48 -04:00
Matt Mankins e0bc3dd51f docs: update example keyserver host in SECURITY.md (#5460) 2025-05-30 08:46:08 -04:00
Bronek Kozicki dacecd24ba Fix unit build error (#5459)
This change fixes the issue that there is a `using namespace` statement inside a namespace scope.
2025-05-29 20:53:31 +00:00
202 changed files with 15456 additions and 20128 deletions

No files matched your search

+38 -38
View File
@@ -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
-1
View File
@@ -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: |
-1
View File
@@ -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: |
-11
View File
@@ -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.
-1
View File
@@ -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
View File
@@ -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
-33
View File
@@ -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
-1
View File
@@ -65,7 +65,6 @@ target_link_libraries(xrpl.imports.main
xrpl.libpb
xxHash::xxhash
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
wamr::wamr
)
include(add_module)
-10
View File
@@ -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
View File
@@ -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
-6
View File
@@ -1,6 +0,0 @@
patches:
2.2.0:
- patch_description: add metering to iwasm interpreter
patch_file: patches/ripp_metering.patch
patch_type: conan
-91
View File
@@ -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
View File
@@ -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:
+33 -7
View File
@@ -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;
}
};
+4 -1
View File
@@ -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.
+2 -2
View File
@@ -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()),
-6
View File
@@ -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;
+6
View File
@@ -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)
-6
View File
@@ -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;
+3 -1
View File
@@ -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.
+1 -2
View File
@@ -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;
-4
View File
@@ -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;
+3
View File
@@ -28,6 +28,9 @@
namespace ripple {
bool
isFeatureEnabled(uint256 const& feature);
class DigestAwareReadView;
/** Rules controlling protocol behavior. */
+6
View File
@@ -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
+2 -3
View File
@@ -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,
};
//------------------------------------------------------------------------------
+5 -11
View File
@@ -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;
+8 -26
View File
@@ -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;
};
+3
View File
@@ -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();
+1 -2
View File
@@ -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;
+5 -1
View File
@@ -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},
+4 -7
View File
@@ -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
+1 -3
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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
+13 -6
View File
@@ -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&
+8
View File
@@ -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
+156
View File
@@ -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
{
+2 -1
View File
@@ -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);
-4
View File
@@ -78,10 +78,6 @@ STValidation::validationFormat()
{sfBaseFeeDrops, soeOPTIONAL},
{sfReserveBaseDrops, soeOPTIONAL},
{sfReserveIncrementDrops, soeOPTIONAL},
// featureSmartEscrow
{sfExtensionComputeLimit, soeOPTIONAL},
{sfExtensionSizeLimit, soeOPTIONAL},
{sfGasPrice, soeOPTIONAL},
};
// clang-format on
+2 -3
View File
@@ -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."),
+7 -8
View File
@@ -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
View File
@@ -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);
+8 -7
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+36 -29
View File
@@ -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{
+141
View File
@@ -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
+5 -2
View File
@@ -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
View File
@@ -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
+3 -1
View File
@@ -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);
}
};
+8 -7
View File
@@ -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));
+2 -1
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+4 -46
View File
@@ -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);
}
}
+10 -5
View File
@@ -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);
}
};
+98 -3
View File
@@ -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);
}
};
-9
View File
@@ -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);
+624
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+29 -11
View File
@@ -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
+1 -8
View File
@@ -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();
}
+2 -1
View File
@@ -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);
}
};
+1
View File
@@ -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);
+2 -1
View File
@@ -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);
}
};
+13 -2
View File
@@ -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"};
-801
View File
@@ -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";
};
+3
View File
@@ -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
View File
@@ -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 {
+14
View File
@@ -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
+15
View File
@@ -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);
-191
View File
@@ -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 */
/******************************************************************************/
+7 -5
View File
@@ -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
+185
View File
@@ -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
+3
View File
@@ -96,6 +96,9 @@ private:
case asfDisallowIncomingTrustline:
mask_ |= lsfDisallowIncomingTrustline;
break;
case asfAllowTrustLineLocking:
mask_ |= lsfAllowTrustLineLocking;
break;
default:
Throw<std::runtime_error>("unknown flag");
}
+11 -6
View File
@@ -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())
{
}
+21 -4
View File
@@ -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);
+42
View File
@@ -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
{
-36
View File
@@ -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
+36
View File
@@ -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
-5
View File
@@ -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;
+82
View File
@@ -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
+15
View File
@@ -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;
+85
View File
@@ -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
+51
View File
@@ -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
+11 -7
View File
@@ -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);
}
};
+4 -2
View File
@@ -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)));
+349 -44
View File
@@ -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();
}
};
+2 -1
View File
@@ -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);
}
};
+3 -2
View File
@@ -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)
<< " ";
+454 -35
View File
@@ -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();
}
};
+368
View File
@@ -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