mirror of
https://github.com/XRPLF/rippled.git
synced 2026-10-21 19:18:22 +00:00
Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bdf5fa8b8 | ||
|
|
65b0b976d9 | ||
|
|
a0d275feec | ||
|
|
ece3a8d7be | ||
|
|
463acf51b5 | ||
|
|
1cd16fab87 | ||
|
|
add55c4f33 | ||
|
|
51a9c0ff59 | ||
|
|
6e8a5f0f4e | ||
|
|
8a33702f26 | ||
|
|
a072d49802 | ||
|
|
a0aeeb8e07 | ||
|
|
383b225690 | ||
|
|
ace2247800 | ||
|
|
6a6fed5dce | ||
|
|
1f8aece8cd | ||
|
|
6c6f8cd4f9 | ||
|
|
fb1311e013 | ||
|
|
ce31acf030 | ||
|
|
31ad5ac63b | ||
|
|
1ede0bdec4 | ||
|
|
aef32ead2c | ||
|
|
5b43ec7f73 | ||
|
|
1e9ff88a00 | ||
|
|
bb9bb5f5c5 | ||
|
|
c533abd8b6 | ||
|
|
bb9bc764bc | ||
|
|
b4b53a6cb7 | ||
|
|
9c0204906c | ||
|
|
4670b373c1 | ||
|
|
f03b5883bd | ||
|
|
f8b2fe4dd5 | ||
|
|
be4a0c9c2b | ||
|
|
f37d52d8e9 | ||
|
|
177cdaf550 | ||
|
|
1573a443b7 | ||
|
|
911c0466c0 | ||
|
|
b6a95f9970 |
No files matched your search
+38
-38
@@ -1,5 +1,5 @@
|
||||
---
|
||||
Language: Cpp
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
@@ -19,52 +19,52 @@ AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ColumnLimit: 80
|
||||
CommentPragmas: "^ IWYU pragma:"
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
ForEachMacros: [ Q_FOREACH, BOOST_FOREACH ]
|
||||
IncludeBlocks: Regroup
|
||||
ForEachMacros: [Q_FOREACH, BOOST_FOREACH]
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^<(test)/'
|
||||
Priority: 0
|
||||
- Regex: '^<(xrpld)/'
|
||||
Priority: 1
|
||||
- Regex: '^<(xrpl)/'
|
||||
Priority: 2
|
||||
- Regex: '^<(boost)/'
|
||||
Priority: 3
|
||||
- Regex: '^.*/'
|
||||
Priority: 4
|
||||
- Regex: '^.*\.h'
|
||||
Priority: 5
|
||||
- Regex: '.*'
|
||||
Priority: 6
|
||||
IncludeIsMainRegex: '$'
|
||||
- Regex: "^<(test)/"
|
||||
Priority: 0
|
||||
- Regex: "^<(xrpld)/"
|
||||
Priority: 1
|
||||
- Regex: "^<(xrpl)/"
|
||||
Priority: 2
|
||||
- Regex: "^<(boost)/"
|
||||
Priority: 3
|
||||
- Regex: "^.*/"
|
||||
Priority: 4
|
||||
- Regex: '^.*\.h'
|
||||
Priority: 5
|
||||
- Regex: ".*"
|
||||
Priority: 6
|
||||
IncludeIsMainRegex: "$"
|
||||
IndentCaseLabels: true
|
||||
IndentFunctionDeclarationAfterType: false
|
||||
IndentRequiresClause: true
|
||||
IndentWidth: 4
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
@@ -78,20 +78,20 @@ PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
ReflowComments: true
|
||||
RequiresClausePosition: OwnLine
|
||||
SortIncludes: true
|
||||
SortIncludes: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
QualifierAlignment: Right
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
QualifierAlignment: Right
|
||||
@@ -1,8 +1,2 @@
|
||||
# Allow anyone to review any change by default.
|
||||
*
|
||||
|
||||
# Require the rpc-reviewers team to review changes to the rpc code.
|
||||
include/xrpl/protocol/ @xrplf/rpc-reviewers
|
||||
src/libxrpl/protocol/ @xrplf/rpc-reviewers
|
||||
src/xrpld/rpc/ @xrplf/rpc-reviewers
|
||||
src/xrpld/app/misc/ @xrplf/rpc-reviewers
|
||||
@@ -17,6 +17,7 @@ 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 external/wamr wamr/2.3.1@
|
||||
- name: add Ripple Conan remote
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -411,6 +411,7 @@ 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 external/wamr wamr/2.3.1@
|
||||
|
||||
- name: build dependencies
|
||||
run: |
|
||||
|
||||
@@ -204,6 +204,17 @@ 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.3.1@
|
||||
# Conan 2.x
|
||||
conan export --version 2.3.1 external/wamr
|
||||
```
|
||||
|
||||
### Build and Test
|
||||
|
||||
1. Create a build directory and move into it.
|
||||
|
||||
@@ -120,6 +120,7 @@ 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
|
||||
|
||||
@@ -1249,6 +1249,39 @@
|
||||
# Example:
|
||||
# owner_reserve = 2000000 # 2 XRP
|
||||
#
|
||||
# extension_compute_limit = <gas>
|
||||
#
|
||||
# The extension compute limit is the maximum amount of gas that can be
|
||||
# consumed by a single transaction. The gas limit is used to prevent
|
||||
# transactions from consuming too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, rippled will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# extension_compute_limit = 1000000 # 1 million gas
|
||||
#
|
||||
# extension_size_limit = <bytes>
|
||||
#
|
||||
# The extension size limit is the maximum size of a WASM extension in
|
||||
# bytes. The size limit is used to prevent extensions from consuming
|
||||
# too many resources.
|
||||
#
|
||||
# If this parameter is unspecified, rippled will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# extension_size_limit = 100000 # 100 kb
|
||||
#
|
||||
# gas_price = <bytes>
|
||||
#
|
||||
# The gas price is the conversion between WASM gas and its price in drops.
|
||||
#
|
||||
# If this parameter is unspecified, rippled will use an internal
|
||||
# default. Don't change this without understanding the consequences.
|
||||
#
|
||||
# Example:
|
||||
# gas_price = 1000000 # 1 drop per gas
|
||||
#-------------------------------------------------------------------------------
|
||||
#
|
||||
# 9. Misc Settings
|
||||
|
||||
@@ -65,6 +65,7 @@ target_link_libraries(xrpl.imports.main
|
||||
xrpl.libpb
|
||||
xxHash::xxhash
|
||||
$<$<BOOL:${voidstar}>:antithesis-sdk-cpp>
|
||||
wamr::wamr
|
||||
)
|
||||
|
||||
include(add_module)
|
||||
|
||||
@@ -2,6 +2,7 @@ from conan import ConanFile, __version__ as conan_version
|
||||
from conan.tools.cmake import CMake, CMakeToolchain, cmake_layout
|
||||
import re
|
||||
|
||||
|
||||
class Xrpl(ConanFile):
|
||||
name = 'xrpl'
|
||||
|
||||
@@ -31,6 +32,7 @@ class Xrpl(ConanFile):
|
||||
'openssl/1.1.1v',
|
||||
'soci/4.0.3',
|
||||
'zlib/1.3.1',
|
||||
'wamr/2.3.1',
|
||||
]
|
||||
|
||||
tool_requires = [
|
||||
@@ -128,6 +130,7 @@ class Xrpl(ConanFile):
|
||||
self.folders.generators = 'build/generators'
|
||||
|
||||
generators = 'CMakeDeps'
|
||||
|
||||
def generate(self):
|
||||
tc = CMakeToolchain(self)
|
||||
tc.variables['tests'] = self.options.tests
|
||||
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
patches:
|
||||
2.3.1:
|
||||
- patch_description: add metering to iwasm interpreter
|
||||
patch_file: patches/ripp_metering.patch
|
||||
patch_type: conan
|
||||
|
||||
Vendored
+92
@@ -0,0 +1,92 @@
|
||||
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.3.1"
|
||||
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",
|
||||
"2a303861cc916dc182b7fecaa0aacc1b797e7ac6",
|
||||
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"
|
||||
|
||||
+724
@@ -0,0 +1,724 @@
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 88a1642b..aeb29912 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/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c
|
||||
index b2c9ed62..87947a18 100644
|
||||
--- a/core/iwasm/aot/aot_runtime.c
|
||||
+++ b/core/iwasm/aot/aot_runtime.c
|
||||
@@ -5484,7 +5484,7 @@ aot_resolve_import_func(AOTModule *module, AOTImportFunc *import_func)
|
||||
import_func->func_ptr_linked = wasm_native_resolve_symbol(
|
||||
import_func->module_name, import_func->func_name,
|
||||
import_func->func_type, &import_func->signature,
|
||||
- &import_func->attachment, &import_func->call_conv_raw);
|
||||
+ &import_func->attachment, NULL, &import_func->call_conv_raw);
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
if (!import_func->func_ptr_linked) {
|
||||
if (!wasm_runtime_is_built_in_module(import_func->module_name)) {
|
||||
diff --git a/core/iwasm/common/wasm_c_api.c b/core/iwasm/common/wasm_c_api.c
|
||||
index 269ec577..34eb7c34 100644
|
||||
--- a/core/iwasm/common/wasm_c_api.c
|
||||
+++ b/core/iwasm/common/wasm_c_api.c
|
||||
@@ -3242,10 +3242,20 @@ wasm_func_copy(const wasm_func_t *func)
|
||||
|
||||
cloned->func_idx_rt = func->func_idx_rt;
|
||||
cloned->inst_comm_rt = func->inst_comm_rt;
|
||||
+ cloned->gas = func->gas;
|
||||
|
||||
RETURN_OBJ(cloned, wasm_func_delete)
|
||||
}
|
||||
|
||||
+uint32_t
|
||||
+wasm_func_set_gas(wasm_func_t *func, uint32_t gas)
|
||||
+{
|
||||
+ if(!func) return 0;
|
||||
+
|
||||
+ func->gas = gas;
|
||||
+ return gas;
|
||||
+}
|
||||
+
|
||||
own wasm_functype_t *
|
||||
wasm_func_type(const wasm_func_t *func)
|
||||
{
|
||||
@@ -4998,11 +5008,11 @@ wasm_instance_new_with_args_ex(wasm_store_t *store, const wasm_module_t *module,
|
||||
goto failed;
|
||||
}
|
||||
|
||||
+ WASMModuleInstance *wasm_module_inst = NULL;
|
||||
/* create the c-api func import list */
|
||||
#if WASM_ENABLE_INTERP != 0
|
||||
if (instance->inst_comm_rt->module_type == Wasm_Module_Bytecode) {
|
||||
- WASMModuleInstance *wasm_module_inst =
|
||||
- (WASMModuleInstance *)instance->inst_comm_rt;
|
||||
+ wasm_module_inst = (WASMModuleInstance *)instance->inst_comm_rt;
|
||||
p_func_imports = &(wasm_module_inst->c_api_func_imports);
|
||||
import_func_count = MODULE_INTERP(module)->import_function_count;
|
||||
}
|
||||
@@ -5052,6 +5062,13 @@ wasm_instance_new_with_args_ex(wasm_store_t *store, const wasm_module_t *module,
|
||||
}
|
||||
bh_assert(func_import->func_ptr_linked);
|
||||
|
||||
+ // fill gas
|
||||
+ if(wasm_module_inst) {
|
||||
+ WASMFunctionInstance *fi = wasm_module_inst->e->functions + func_host->func_idx_rt;
|
||||
+ if(fi) fi->gas = func_host->gas;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
func_import++;
|
||||
}
|
||||
|
||||
@@ -5389,3 +5406,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_c_api_internal.h b/core/iwasm/common/wasm_c_api_internal.h
|
||||
index 49a17a96..19a85980 100644
|
||||
--- a/core/iwasm/common/wasm_c_api_internal.h
|
||||
+++ b/core/iwasm/common/wasm_c_api_internal.h
|
||||
@@ -142,6 +142,10 @@ struct wasm_func_t {
|
||||
void (*finalizer)(void *);
|
||||
} cb_env;
|
||||
} u;
|
||||
+
|
||||
+ // gas cost for import func
|
||||
+ uint32 gas;
|
||||
+
|
||||
/*
|
||||
* an index in both functions runtime instance lists
|
||||
* of interpreter mode and aot mode
|
||||
diff --git a/core/iwasm/common/wasm_exec_env.c b/core/iwasm/common/wasm_exec_env.c
|
||||
index 47752950..d5821c4f 100644
|
||||
--- a/core/iwasm/common/wasm_exec_env.c
|
||||
+++ b/core/iwasm/common/wasm_exec_env.c
|
||||
@@ -86,7 +86,9 @@ wasm_exec_env_create_internal(struct WASMModuleInstanceCommon *module_inst,
|
||||
#endif
|
||||
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
- exec_env->instructions_to_execute = -1;
|
||||
+ exec_env->instructions_to_execute = INT64_MAX;
|
||||
+ for(int i = 0; i < 256; ++i)
|
||||
+ exec_env->instructions_schedule[i] = 1;
|
||||
#endif
|
||||
|
||||
return exec_env;
|
||||
diff --git a/core/iwasm/common/wasm_exec_env.h b/core/iwasm/common/wasm_exec_env.h
|
||||
index 5d80312f..2713a092 100644
|
||||
--- a/core/iwasm/common/wasm_exec_env.h
|
||||
+++ b/core/iwasm/common/wasm_exec_env.h
|
||||
@@ -89,7 +89,8 @@ typedef struct WASMExecEnv {
|
||||
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
/* instructions to execute */
|
||||
- int instructions_to_execute;
|
||||
+ int64 instructions_to_execute;
|
||||
+ int64 instructions_schedule[256];
|
||||
#endif
|
||||
|
||||
#if WASM_ENABLE_FAST_JIT != 0
|
||||
diff --git a/core/iwasm/common/wasm_native.c b/core/iwasm/common/wasm_native.c
|
||||
index 060bb2c3..9221c36a 100644
|
||||
--- a/core/iwasm/common/wasm_native.c
|
||||
+++ b/core/iwasm/common/wasm_native.c
|
||||
@@ -180,9 +180,9 @@ native_symbol_cmp(const void *native_symbol1, const void *native_symbol2)
|
||||
((const NativeSymbol *)native_symbol2)->symbol);
|
||||
}
|
||||
|
||||
-static void *
|
||||
+static NativeSymbol *
|
||||
lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols,
|
||||
- const char *symbol, const char **p_signature, void **p_attachment)
|
||||
+ const char *symbol)
|
||||
{
|
||||
NativeSymbol *native_symbol, key = { 0 };
|
||||
|
||||
@@ -190,9 +190,7 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols,
|
||||
|
||||
if ((native_symbol = bsearch(&key, native_symbols, n_native_symbols,
|
||||
sizeof(NativeSymbol), native_symbol_cmp))) {
|
||||
- *p_signature = native_symbol->signature;
|
||||
- *p_attachment = native_symbol->attachment;
|
||||
- return native_symbol->func_ptr;
|
||||
+ return native_symbol;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -205,25 +203,36 @@ lookup_symbol(NativeSymbol *native_symbols, uint32 n_native_symbols,
|
||||
void *
|
||||
wasm_native_resolve_symbol(const char *module_name, const char *field_name,
|
||||
const WASMFuncType *func_type,
|
||||
- const char **p_signature, void **p_attachment,
|
||||
+ const char **p_signature, void **p_attachment, uint32_t *gas,
|
||||
bool *p_call_conv_raw)
|
||||
{
|
||||
NativeSymbolsNode *node, *node_next;
|
||||
const char *signature = NULL;
|
||||
void *func_ptr = NULL, *attachment = NULL;
|
||||
+ NativeSymbol *native_symbol = NULL;
|
||||
|
||||
node = g_native_symbols_list;
|
||||
while (node) {
|
||||
node_next = node->next;
|
||||
if (!strcmp(node->module_name, module_name)) {
|
||||
- if ((func_ptr =
|
||||
+ if ((native_symbol =
|
||||
lookup_symbol(node->native_symbols, node->n_native_symbols,
|
||||
- field_name, &signature, &attachment))
|
||||
+ field_name))
|
||||
|| (field_name[0] == '_'
|
||||
- && (func_ptr = lookup_symbol(
|
||||
+ && (native_symbol = lookup_symbol(
|
||||
node->native_symbols, node->n_native_symbols,
|
||||
- field_name + 1, &signature, &attachment))))
|
||||
- break;
|
||||
+ field_name + 1))))
|
||||
+ {
|
||||
+ func_ptr = native_symbol->func_ptr;
|
||||
+ if(func_ptr)
|
||||
+ {
|
||||
+ if(gas)
|
||||
+ *gas = native_symbol->gas;
|
||||
+ signature = native_symbol->signature;
|
||||
+ attachment = native_symbol->attachment;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
node = node_next;
|
||||
}
|
||||
diff --git a/core/iwasm/common/wasm_native.h b/core/iwasm/common/wasm_native.h
|
||||
index 9a6afee1..0fe4739f 100644
|
||||
--- a/core/iwasm/common/wasm_native.h
|
||||
+++ b/core/iwasm/common/wasm_native.h
|
||||
@@ -52,7 +52,7 @@ wasm_native_lookup_libc_builtin_global(const char *module_name,
|
||||
void *
|
||||
wasm_native_resolve_symbol(const char *module_name, const char *field_name,
|
||||
const WASMFuncType *func_type,
|
||||
- const char **p_signature, void **p_attachment,
|
||||
+ const char **p_signature, void **p_attachment, uint32_t *gas,
|
||||
bool *p_call_conv_raw);
|
||||
|
||||
bool
|
||||
diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c
|
||||
index dcee0aea..10c516d6 100644
|
||||
--- a/core/iwasm/common/wasm_runtime_common.c
|
||||
+++ b/core/iwasm/common/wasm_runtime_common.c
|
||||
@@ -2288,10 +2288,26 @@ wasm_runtime_access_exce_check_guard_page()
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
void
|
||||
wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
|
||||
- int instructions_to_execute)
|
||||
+ int64 instructions_to_execute)
|
||||
{
|
||||
+ if(instructions_to_execute == -1)
|
||||
+ instructions_to_execute = INT64_MAX;
|
||||
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 *
|
||||
@@ -7348,7 +7364,7 @@ wasm_runtime_is_import_func_linked(const char *module_name,
|
||||
const char *func_name)
|
||||
{
|
||||
return wasm_native_resolve_symbol(module_name, func_name, NULL, NULL, NULL,
|
||||
- NULL);
|
||||
+ NULL, NULL);
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -7805,13 +7821,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;
|
||||
@@ -7821,6 +7838,7 @@ wasm_runtime_detect_native_stack_overflow(WASMExecEnv *exec_env)
|
||||
"native stack overflow");
|
||||
return false;
|
||||
}
|
||||
+#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7843,7 +7861,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 64a6cd79..f4c55e2f 100644
|
||||
--- a/core/iwasm/common/wasm_runtime_common.h
|
||||
+++ b/core/iwasm/common/wasm_runtime_common.h
|
||||
@@ -795,7 +795,14 @@ wasm_runtime_set_native_stack_boundary(WASMExecEnv *exec_env,
|
||||
/* See wasm_export.h for description */
|
||||
WASM_RUNTIME_API_EXTERN void
|
||||
wasm_runtime_set_instruction_count_limit(WASMExecEnv *exec_env,
|
||||
- int instructions_to_execute);
|
||||
+ 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
|
||||
diff --git a/core/iwasm/include/lib_export.h b/core/iwasm/include/lib_export.h
|
||||
index 0ca668f5..93bcf807 100644
|
||||
--- a/core/iwasm/include/lib_export.h
|
||||
+++ b/core/iwasm/include/lib_export.h
|
||||
@@ -24,6 +24,8 @@ typedef struct NativeSymbol {
|
||||
/* attachment which can be retrieved in native API by
|
||||
calling wasm_runtime_get_function_attachment(exec_env) */
|
||||
void *attachment;
|
||||
+ // gas cost for import func
|
||||
+ uint32_t gas;
|
||||
} NativeSymbol;
|
||||
|
||||
/* clang-format off */
|
||||
diff --git a/core/iwasm/include/wasm_c_api.h b/core/iwasm/include/wasm_c_api.h
|
||||
index 241a0eec..1141744c 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
|
||||
@@ -592,6 +594,8 @@ WASM_API_EXTERN size_t wasm_func_result_arity(const wasm_func_t*);
|
||||
WASM_API_EXTERN own wasm_trap_t* wasm_func_call(
|
||||
const wasm_func_t*, const wasm_val_vec_t* args, wasm_val_vec_t* results);
|
||||
|
||||
+WASM_API_EXTERN own uint32_t wasm_func_set_gas(wasm_func_t*, uint32_t);
|
||||
+
|
||||
|
||||
// Global Instances
|
||||
|
||||
@@ -701,6 +705,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 b4ab34be..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")))
|
||||
@@ -1833,7 +1835,14 @@ wasm_runtime_set_native_stack_boundary(wasm_exec_env_t exec_env,
|
||||
*/
|
||||
WASM_RUNTIME_API_EXTERN void
|
||||
wasm_runtime_set_instruction_count_limit(wasm_exec_env_t exec_env,
|
||||
- int instruction_count);
|
||||
+ 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:
|
||||
diff --git a/core/iwasm/interpreter/wasm.h b/core/iwasm/interpreter/wasm.h
|
||||
index ddc0b15b..3a707878 100644
|
||||
--- a/core/iwasm/interpreter/wasm.h
|
||||
+++ b/core/iwasm/interpreter/wasm.h
|
||||
@@ -579,6 +579,9 @@ typedef struct WASMFunctionImport {
|
||||
WASMModule *import_module;
|
||||
WASMFunction *import_func_linked;
|
||||
#endif
|
||||
+ // gas cost for import func
|
||||
+ uint32 gas;
|
||||
+
|
||||
} WASMFunctionImport;
|
||||
|
||||
#if WASM_ENABLE_TAGS != 0
|
||||
diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c
|
||||
index 1e98b0fa..ae24ff8b 100644
|
||||
--- a/core/iwasm/interpreter/wasm_interp_classic.c
|
||||
+++ b/core/iwasm/interpreter/wasm_interp_classic.c
|
||||
@@ -1569,13 +1569,14 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
|
||||
}
|
||||
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
-#define CHECK_INSTRUCTION_LIMIT() \
|
||||
- if (instructions_left == 0) { \
|
||||
- wasm_set_exception(module, "instruction limit exceeded"); \
|
||||
- goto got_exception; \
|
||||
- } \
|
||||
- else if (instructions_left > 0) \
|
||||
- instructions_left--;
|
||||
+#define CHECK_INSTRUCTION_LIMIT() \
|
||||
+ do { \
|
||||
+ instructions_left -= instructions_schedule[opcode]; \
|
||||
+ if (instructions_left < 0) { \
|
||||
+ wasm_set_exception(module, "instruction limit exceeded"); \
|
||||
+ goto got_exception; \
|
||||
+ } \
|
||||
+ } while (0)
|
||||
#else
|
||||
#define CHECK_INSTRUCTION_LIMIT() (void)0
|
||||
#endif
|
||||
@@ -1625,9 +1626,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
uint32 cache_index, type_index, param_cell_num, cell_num;
|
||||
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
- int instructions_left = -1;
|
||||
+ int64 instructions_left = INT64_MAX;
|
||||
+ int64 const *instructions_schedule = NULL;
|
||||
if (exec_env) {
|
||||
instructions_left = exec_env->instructions_to_execute;
|
||||
+ instructions_schedule = exec_env->instructions_schedule;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -6885,6 +6888,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_left;
|
||||
+#endif
|
||||
+
|
||||
if (!prev_frame->ip) {
|
||||
/* Called from native. */
|
||||
return;
|
||||
@@ -6925,6 +6933,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_left;
|
||||
+#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 4e5edf41..04c39e1f 100644
|
||||
--- a/core/iwasm/interpreter/wasm_interp_fast.c
|
||||
+++ b/core/iwasm/interpreter/wasm_interp_fast.c
|
||||
@@ -106,14 +106,14 @@ typedef float64 CellType_F64;
|
||||
} while (0)
|
||||
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
-#define CHECK_INSTRUCTION_LIMIT() \
|
||||
- if (instructions_left == 0) { \
|
||||
- wasm_set_exception(module, "instruction limit exceeded"); \
|
||||
- goto got_exception; \
|
||||
- } \
|
||||
- else if (instructions_left > 0) \
|
||||
- instructions_left--;
|
||||
-
|
||||
+#define CHECK_INSTRUCTION_LIMIT() \
|
||||
+ do { \
|
||||
+ instructions_left -= instructions_schedule[opcode]; \
|
||||
+ if (instructions_left < 0) { \
|
||||
+ wasm_set_exception(module, "instruction limit exceeded"); \
|
||||
+ goto got_exception; \
|
||||
+ } \
|
||||
+ } while (0)
|
||||
#else
|
||||
#define CHECK_INSTRUCTION_LIMIT() (void)0
|
||||
#endif
|
||||
@@ -1454,7 +1454,6 @@ wasm_interp_dump_op_count()
|
||||
do { \
|
||||
const void *p_label_addr = *(void **)frame_ip; \
|
||||
frame_ip += sizeof(void *); \
|
||||
- CHECK_INSTRUCTION_LIMIT(); \
|
||||
goto *p_label_addr; \
|
||||
} while (0)
|
||||
#else
|
||||
@@ -1466,7 +1465,6 @@ wasm_interp_dump_op_count()
|
||||
/* int32 relative offset was emitted in 64-bit target */ \
|
||||
p_label_addr = label_base + (int32)LOAD_U32_WITH_2U16S(frame_ip); \
|
||||
frame_ip += sizeof(int32); \
|
||||
- CHECK_INSTRUCTION_LIMIT(); \
|
||||
goto *p_label_addr; \
|
||||
} while (0)
|
||||
#else
|
||||
@@ -1477,17 +1475,18 @@ wasm_interp_dump_op_count()
|
||||
/* uint32 label address was emitted in 32-bit target */ \
|
||||
p_label_addr = (void *)(uintptr_t)LOAD_U32_WITH_2U16S(frame_ip); \
|
||||
frame_ip += sizeof(int32); \
|
||||
- CHECK_INSTRUCTION_LIMIT(); \
|
||||
goto *p_label_addr; \
|
||||
} 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 */
|
||||
|
||||
@@ -1508,6 +1507,25 @@ get_global_addr(uint8 *global_data, WASMGlobalInstance *global)
|
||||
#endif
|
||||
}
|
||||
|
||||
+static int64 const def_instructions_schedule[256] = {
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
|
||||
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1
|
||||
+};
|
||||
+
|
||||
static void
|
||||
wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
WASMExecEnv *exec_env,
|
||||
@@ -1556,9 +1574,11 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
uint8 opcode = 0, local_type, *global_addr;
|
||||
|
||||
#if WASM_ENABLE_INSTRUCTION_METERING != 0
|
||||
- int instructions_left = -1;
|
||||
+ int64 instructions_left = INT64_MAX;
|
||||
+ int64 const *instructions_schedule = def_instructions_schedule;
|
||||
if (exec_env) {
|
||||
instructions_left = exec_env->instructions_to_execute;
|
||||
+ instructions_schedule = exec_env->instructions_schedule;
|
||||
}
|
||||
#endif
|
||||
#if !defined(OS_ENABLE_HW_BOUND_CHECK) \
|
||||
@@ -7694,6 +7714,7 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module,
|
||||
{
|
||||
wasm_interp_call_func_native(module, exec_env, cur_func,
|
||||
prev_frame);
|
||||
+ instructions_left -= cur_func->gas;
|
||||
}
|
||||
|
||||
#if WASM_ENABLE_TAIL_CALL != 0 || WASM_ENABLE_GC != 0
|
||||
@@ -7806,6 +7827,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_left;
|
||||
+#endif
|
||||
+
|
||||
if (!prev_frame->ip)
|
||||
/* Called from native. */
|
||||
return;
|
||||
@@ -7834,6 +7860,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_left;
|
||||
+#endif
|
||||
return;
|
||||
|
||||
#if WASM_ENABLE_LABELS_AS_VALUES == 0
|
||||
diff --git a/core/iwasm/interpreter/wasm_mini_loader.c b/core/iwasm/interpreter/wasm_mini_loader.c
|
||||
index e66c08ba..d52e677e 100644
|
||||
--- a/core/iwasm/interpreter/wasm_mini_loader.c
|
||||
+++ b/core/iwasm/interpreter/wasm_mini_loader.c
|
||||
@@ -636,6 +636,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end,
|
||||
const char *linked_signature = NULL;
|
||||
void *linked_attachment = NULL;
|
||||
bool linked_call_conv_raw = false;
|
||||
+ uint32_t gas = 0;
|
||||
|
||||
read_leb_uint32(p, p_end, declare_type_index);
|
||||
*p_buf = p;
|
||||
@@ -647,7 +648,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end,
|
||||
/* check built-in modules */
|
||||
linked_func = wasm_native_resolve_symbol(
|
||||
sub_module_name, function_name, declare_func_type, &linked_signature,
|
||||
- &linked_attachment, &linked_call_conv_raw);
|
||||
+ &linked_attachment, &gas, &linked_call_conv_raw);
|
||||
|
||||
function->module_name = (char *)sub_module_name;
|
||||
function->field_name = (char *)function_name;
|
||||
@@ -656,6 +657,7 @@ load_function_import(const uint8 **p_buf, const uint8 *buf_end,
|
||||
function->signature = linked_signature;
|
||||
function->attachment = linked_attachment;
|
||||
function->call_conv_raw = linked_call_conv_raw;
|
||||
+ function->gas = gas;
|
||||
return true;
|
||||
}
|
||||
|
||||
diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c
|
||||
index 3cc2afe0..55859d35 100644
|
||||
--- a/core/iwasm/interpreter/wasm_runtime.c
|
||||
+++ b/core/iwasm/interpreter/wasm_runtime.c
|
||||
@@ -168,7 +168,7 @@ wasm_resolve_import_func(const WASMModule *module, WASMFunctionImport *function)
|
||||
#endif
|
||||
function->func_ptr_linked = wasm_native_resolve_symbol(
|
||||
function->module_name, function->field_name, function->func_type,
|
||||
- &function->signature, &function->attachment, &function->call_conv_raw);
|
||||
+ &function->signature, &function->attachment, &function->gas, &function->call_conv_raw);
|
||||
|
||||
if (function->func_ptr_linked) {
|
||||
return true;
|
||||
@@ -820,6 +820,7 @@ functions_instantiate(const WASMModule *module, WASMModuleInstance *module_inst,
|
||||
function->param_count =
|
||||
(uint16)function->u.func_import->func_type->param_count;
|
||||
function->param_types = function->u.func_import->func_type->types;
|
||||
+ function->gas = import->u.function.gas;
|
||||
function->local_cell_num = 0;
|
||||
function->local_count = 0;
|
||||
function->local_types = NULL;
|
||||
diff --git a/core/iwasm/interpreter/wasm_runtime.h b/core/iwasm/interpreter/wasm_runtime.h
|
||||
index 8d38c883..a687ab89 100644
|
||||
--- a/core/iwasm/interpreter/wasm_runtime.h
|
||||
+++ b/core/iwasm/interpreter/wasm_runtime.h
|
||||
@@ -228,6 +228,10 @@ struct WASMFunctionInstance {
|
||||
WASMFunctionImport *func_import;
|
||||
WASMFunction *func;
|
||||
} u;
|
||||
+
|
||||
+ // gas cost for import func
|
||||
+ uint32 gas;
|
||||
+
|
||||
#if WASM_ENABLE_MULTI_MODULE != 0
|
||||
WASMModuleInstance *import_module_inst;
|
||||
WASMFunctionInstance *import_func_inst;
|
||||
diff --git a/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c b/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c
|
||||
index a68c0749..cafb6915 100644
|
||||
--- a/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c
|
||||
+++ b/core/iwasm/libraries/libc-builtin/libc_builtin_wrapper.c
|
||||
@@ -1038,16 +1038,16 @@ print_f64_wrapper(wasm_exec_env_t exec_env, double f64)
|
||||
|
||||
/* clang-format off */
|
||||
#define REG_NATIVE_FUNC(func_name, signature) \
|
||||
- { #func_name, func_name##_wrapper, signature, NULL }
|
||||
+ { #func_name, func_name##_wrapper, signature, NULL, 0 }
|
||||
/* clang-format on */
|
||||
|
||||
static NativeSymbol native_symbols_libc_builtin[] = {
|
||||
REG_NATIVE_FUNC(printf, "($*)i"),
|
||||
REG_NATIVE_FUNC(sprintf, "($$*)i"),
|
||||
REG_NATIVE_FUNC(snprintf, "(*~$*)i"),
|
||||
- { "vprintf", printf_wrapper, "($*)i", NULL },
|
||||
- { "vsprintf", sprintf_wrapper, "($$*)i", NULL },
|
||||
- { "vsnprintf", snprintf_wrapper, "(*~$*)i", NULL },
|
||||
+ { "vprintf", printf_wrapper, "($*)i", NULL, 0 },
|
||||
+ { "vsprintf", sprintf_wrapper, "($$*)i", NULL, 0 },
|
||||
+ { "vsnprintf", snprintf_wrapper, "(*~$*)i", NULL, 0 },
|
||||
REG_NATIVE_FUNC(puts, "($)i"),
|
||||
REG_NATIVE_FUNC(putchar, "(i)i"),
|
||||
REG_NATIVE_FUNC(memcmp, "(**~)i"),
|
||||
diff --git a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c
|
||||
index 6d057a6a..25879f33 100644
|
||||
--- a/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c
|
||||
+++ b/core/iwasm/libraries/libc-wasi/libc_wasi_wrapper.c
|
||||
@@ -2257,7 +2257,7 @@ wasi_sched_yield(wasm_exec_env_t exec_env)
|
||||
|
||||
/* clang-format off */
|
||||
#define REG_NATIVE_FUNC(func_name, signature) \
|
||||
- { #func_name, wasi_##func_name, signature, NULL }
|
||||
+ { #func_name, wasi_##func_name, signature, NULL, 0 }
|
||||
/* clang-format on */
|
||||
|
||||
static NativeSymbol native_symbols_libc_wasi[] = {
|
||||
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");
|
||||
@@ -451,8 +451,8 @@ mulDivU(Source1 value, Dest mul, Source2 div)
|
||||
}
|
||||
|
||||
using namespace boost::multiprecision;
|
||||
|
||||
uint128_t product;
|
||||
using uint128 = boost::multiprecision::uint128_t;
|
||||
uint128 product;
|
||||
product = multiply(
|
||||
product,
|
||||
static_cast<std::uint64_t>(value.value()),
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
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
|
||||
@@ -34,6 +36,10 @@ 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;
|
||||
|
||||
@@ -230,6 +230,12 @@ 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;
|
||||
|
||||
@@ -149,11 +149,12 @@ mulRatio(
|
||||
bool roundUp)
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
using int128 = boost::multiprecision::int128_t;
|
||||
|
||||
if (!den)
|
||||
Throw<std::runtime_error>("division by zero");
|
||||
|
||||
int128_t const amt128(amt.value());
|
||||
int128 const amt128(amt.value());
|
||||
auto const neg = amt.value() < 0;
|
||||
auto const m = amt128 * num;
|
||||
auto r = m / den;
|
||||
|
||||
@@ -126,6 +126,10 @@ 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;
|
||||
|
||||
|
||||
@@ -141,6 +141,8 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
temBAD_TRANSFER_FEE,
|
||||
temINVALID_INNER_BATCH,
|
||||
|
||||
temBAD_WASM,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -185,6 +187,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefNO_TICKET,
|
||||
tefNFTOKEN_IS_NOT_TRANSFERABLE,
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_WASM,
|
||||
tefWASM_FIELD_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -362,6 +366,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecWASM_REJECTED = 199,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -46,10 +46,7 @@ private:
|
||||
CtorHelper);
|
||||
|
||||
public:
|
||||
TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId = std::nullopt);
|
||||
TxMeta(uint256 const& transactionID, std::uint32_t ledger);
|
||||
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&);
|
||||
@@ -136,7 +133,7 @@ public:
|
||||
void
|
||||
setParentBatchId(uint256 const& parentBatchId)
|
||||
{
|
||||
mParentBatchId = parentBatchId;
|
||||
parentBatchId_ = parentBatchId;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -145,13 +142,33 @@ public:
|
||||
XRPL_ASSERT(
|
||||
hasParentBatchId(),
|
||||
"ripple::TxMeta::getParentBatchId : non-null batch id");
|
||||
return *mParentBatchId;
|
||||
return *parentBatchId_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasParentBatchId() const
|
||||
{
|
||||
return static_cast<bool>(mParentBatchId);
|
||||
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_);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -161,7 +178,8 @@ private:
|
||||
int mResult;
|
||||
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<uint256> mParentBatchId;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<uint256> parentBatchId_;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -286,11 +286,12 @@ mulRatio(
|
||||
bool roundUp)
|
||||
{
|
||||
using namespace boost::multiprecision;
|
||||
using int128 = boost::multiprecision::int128_t;
|
||||
|
||||
if (!den)
|
||||
Throw<std::runtime_error>("division by zero");
|
||||
|
||||
int128_t const amt128(amt.drops());
|
||||
int128 const amt128(amt.drops());
|
||||
auto const neg = amt.drops() < 0;
|
||||
auto const m = amt128 * num;
|
||||
auto r = m / den;
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
// 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)
|
||||
@@ -43,7 +44,6 @@ 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)
|
||||
|
||||
@@ -319,6 +319,12 @@ 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},
|
||||
}))
|
||||
@@ -348,6 +354,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
{sfSourceTag, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
|
||||
@@ -114,6 +114,11 @@ 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)
|
||||
@@ -233,7 +238,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
||||
|
||||
// currency amount (AMM)
|
||||
// currency amount (more)
|
||||
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||
@@ -275,6 +280,7 @@ 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)
|
||||
|
||||
@@ -44,11 +44,13 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::delegatable, ({
|
||||
/** This transaction type creates an escrow object. */
|
||||
TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::delegatable, ({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -58,6 +60,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::delegatable, ({
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfComputationAllowance, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
|
||||
@@ -545,6 +548,9 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::notDelegatable, ({
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the network's negative UNL
|
||||
|
||||
@@ -268,6 +268,9 @@ 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
|
||||
|
||||
@@ -1052,8 +1052,7 @@ parseArray(
|
||||
Json::Value const objectFields(json[i][objectName]);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << json_name << "."
|
||||
<< "[" << i << "]." << objectName;
|
||||
ss << json_name << "." << "[" << i << "]." << objectName;
|
||||
|
||||
auto ret = parseObject(
|
||||
ss.str(), objectFields, nameField, depth + 1, error);
|
||||
|
||||
@@ -78,6 +78,10 @@ STValidation::validationFormat()
|
||||
{sfBaseFeeDrops, soeOPTIONAL},
|
||||
{sfReserveBaseDrops, soeOPTIONAL},
|
||||
{sfReserveIncrementDrops, soeOPTIONAL},
|
||||
// featureSmartEscrow
|
||||
{sfExtensionComputeLimit, soeOPTIONAL},
|
||||
{sfExtensionSizeLimit, soeOPTIONAL},
|
||||
{sfGasPrice, soeOPTIONAL},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ transResults()
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -151,6 +152,8 @@ 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."),
|
||||
@@ -220,6 +223,7 @@ transResults()
|
||||
MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."),
|
||||
MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."),
|
||||
MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."),
|
||||
MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."),
|
||||
|
||||
MAKE_ERROR(terRETRY, "Retry transaction."),
|
||||
MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."),
|
||||
|
||||
@@ -56,9 +56,10 @@ 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)
|
||||
@@ -82,6 +83,9 @@ 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)
|
||||
@@ -97,15 +101,11 @@ TxMeta::TxMeta(
|
||||
{
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId)
|
||||
TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
|
||||
: mTransactionID(transactionID)
|
||||
, mLedger(ledger)
|
||||
, mIndex(static_cast<std::uint32_t>(-1))
|
||||
, mResult(255)
|
||||
, mParentBatchId(parentBatchId)
|
||||
, mNodes(sfAffectedNodes)
|
||||
{
|
||||
mNodes.reserve(32);
|
||||
@@ -241,9 +241,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/misc/WasmVM.h>
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#include <xrpld/ledger/Dir.h>
|
||||
|
||||
@@ -1551,7 +1553,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
Account const dillon{"dillon "};
|
||||
Account const dillon{"dillon"};
|
||||
Account const zelda{"zelda"};
|
||||
|
||||
char const credType[] = "abcde";
|
||||
@@ -1693,6 +1695,655 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateFinishFunctionPreflight()
|
||||
{
|
||||
testcase("Test preflight checks involving FinishFunction");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// #[no_mangle]
|
||||
// pub fn finish() -> bool {
|
||||
// unsafe { host_lib::getLedgerSqn() >= 5}
|
||||
// }
|
||||
static auto wasmHex = ledgerSqnHex;
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, supported_amendments() - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction > max length
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.extension_size_limit = 10; // 10 bytes
|
||||
return cfg;
|
||||
}));
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// 11-byte string
|
||||
std::string longWasmHex = "00112233445566778899AA";
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(longWasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->START_UP = Config::FRESH;
|
||||
return cfg;
|
||||
}));
|
||||
XRPAmount const txnFees = env.current()->fees().base * 10 + 1000;
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(500));
|
||||
|
||||
// Success situations
|
||||
{
|
||||
// FinishFunction + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
escrow::condition(escrow::cb1),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Failure situations (i.e. all other combinations)
|
||||
{
|
||||
// only FinishFunction
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + Condition
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + Condition
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction 0 length
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(""),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction nonexistent host function
|
||||
// pub fn finish() -> bool {
|
||||
// unsafe { host_lib::bad() >= 5 }
|
||||
// }
|
||||
auto const badWasmHex =
|
||||
"0061736d010000000105016000017f02100108686f73745f6c696203626164"
|
||||
"00000302010005030100100611027f00418080c0000b7f00418080c0000b07"
|
||||
"2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64"
|
||||
"03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970"
|
||||
"726f64756365727302086c616e6775616765010452757374000c70726f6365"
|
||||
"737365642d6279010572757374631d312e38352e3120283465623136313235"
|
||||
"3020323032352d30332d31352900490f7461726765745f6665617475726573"
|
||||
"042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72"
|
||||
"65666572656e63652d74797065732b0a6d756c746976616c7565";
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(badWasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_WASM));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishWasmFailures()
|
||||
{
|
||||
testcase("EscrowFinish Smart Escrow failures");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// #[no_mangle]
|
||||
// pub fn finish() -> bool {
|
||||
// unsafe { host_lib::getLedgerSqn() >= 5}
|
||||
// }
|
||||
static auto wasmHex = ledgerSqnHex;
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, supported_amendments() - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
fee(txnFees),
|
||||
escrow::comp_allowance(4),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance > max compute limit
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.extension_compute_limit = 1'000; // in gas
|
||||
return cfg;
|
||||
}));
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
// Run past the flag ledger so that a Fee change vote occurs and
|
||||
// updates FeeSettings. (It also activates all supported
|
||||
// amendments.)
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
|
||||
auto const allowance = 1'001;
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
fee(env.current()->fees().base + allowance),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
{
|
||||
uint32_t const allowance = 10'000;
|
||||
Env env(*this);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
XRPAmount const createFee = env.current()->fees().base + 1000;
|
||||
|
||||
// Escrow with FinishFunction + Condition
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(reqNonexistentField),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// Rejected as wasm code request nonexistent memo field
|
||||
XRPAmount const txnFees = env.current()->fees().base * 34 + 1000;
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
}
|
||||
|
||||
Env env(*this);
|
||||
|
||||
// Run past the flag ledger so that a Fee change vote occurs and
|
||||
// updates FeeSettings. (It also activates all supported
|
||||
// amendments.)
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
// create escrow
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// no ComputationAllowance field
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
ter(tefWASM_FIELD_NOT_INCLUDED));
|
||||
}
|
||||
|
||||
{
|
||||
// not enough fees
|
||||
// This function takes 4 gas
|
||||
// In testing, 1 gas costs 1 drop
|
||||
auto const finishFee = env.current()->fees().base + 3;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(4),
|
||||
ter(telINSUF_FEE_P));
|
||||
}
|
||||
|
||||
{
|
||||
// not enough gas
|
||||
// This function takes 4 gas
|
||||
// In testing, 1 gas costs 1 drop
|
||||
auto const finishFee = env.current()->fees().base + 4;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(2),
|
||||
ter(tecFAILED_PROCESSING));
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance field included w/no FinishFunction on
|
||||
// escrow
|
||||
auto const seq2 = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::finish_time(env.now() + 10s),
|
||||
escrow::cancel_time(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
auto const allowance = 100;
|
||||
env(escrow::finish(carol, alice, seq2),
|
||||
fee(env.current()->fees().base + allowance),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(tefNO_WASM));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishFunction()
|
||||
{
|
||||
testcase("Example escrow function");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// #[no_mangle]
|
||||
// pub fn finish() -> bool {
|
||||
// unsafe { host_lib::getLedgerSqn() >= 5}
|
||||
// }
|
||||
static auto wasmHex = ledgerSqnHex;
|
||||
std::uint32_t constexpr allowance = 4;
|
||||
|
||||
{
|
||||
// basic FinishFunction situation
|
||||
Env env(*this);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
XRPAmount txnFees = env.current()->fees().base + 1000;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - txnFees));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
fee(txnFees),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + Condition
|
||||
Env env(*this);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
auto const seq = env.seq(alice);
|
||||
// create escrow
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
XRPAmount const createFee = env.current()->fees().base + 1000;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 34 + 1000;
|
||||
|
||||
// no fulfillment provided, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// fulfillment provided, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
// no fulfillment provided, function succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// wrong fulfillment provided, function succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb2),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// fulfillment provided, function succeeds, tx succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + FinishAfter
|
||||
Env env(*this);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
XRPAmount txnFees = env.current()->fees().base + 1000;
|
||||
auto const ts = env.now() + 97s;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(ts),
|
||||
escrow::cancel_time(env.now() + 1000s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - txnFees));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// finish time hasn't passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(4),
|
||||
fee(txnFees + 1),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
// finish time hasn't passed, function succeeds
|
||||
for (; env.now() < ts; env.close())
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(4),
|
||||
fee(txnFees + 2),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(4),
|
||||
fee(txnFees + 1),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + FinishAfter #2
|
||||
Env env(*this);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
XRPAmount txnFees = env.current()->fees().base + 1000;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
// Don't close the ledger here
|
||||
|
||||
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - txnFees));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// finish time hasn't passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// finish time has passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
// finish time has passed, function succeeds, tx succeeds
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAllHostFunctions()
|
||||
{
|
||||
testcase("Test all host functions");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// TODO: create wasm module for all host functions
|
||||
static auto wasmHex = allHostFunctionsHex;
|
||||
// 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
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
// basic FinishFunction situation
|
||||
Env env(*this);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(1000));
|
||||
XRPAmount txnFees = env.current()->fees().base + 1000;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 11s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
escrow::data("1000000000"), // 1000 XRP in drops
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - txnFees));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// TODO: figure out why this can't be 2412
|
||||
auto const allowance = 3'600;
|
||||
|
||||
// FinishAfter time hasn't passed
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// tx sender not escrow creator (alice)
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
|
||||
// destination balance is too high
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
|
||||
env.close();
|
||||
|
||||
// reduce the destination balance
|
||||
env(pay(carol, alice, XRP(4500)));
|
||||
env.close();
|
||||
|
||||
// tx sender not escrow creator (alice)
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(txnFees),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == 487);
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
@@ -1708,6 +2359,12 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
testConsequences(features);
|
||||
testEscrowWithTickets(features);
|
||||
testCredentials(features);
|
||||
testCreateFinishFunctionPreflight();
|
||||
testFinishWasmFailures();
|
||||
testFinishFunction();
|
||||
|
||||
// TODO: Update module with new host functions
|
||||
testAllHostFunctions();
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -1718,7 +2375,7 @@ public:
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
testWithFeats(all);
|
||||
testWithFeats(all - featureTokenEscrow);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Escrow, app, ripple);
|
||||
|
||||
@@ -37,36 +37,62 @@ 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"});
|
||||
"owner_reserve = 1234",
|
||||
"extension_compute_limit = 100",
|
||||
"extension_size_limit = 200",
|
||||
" gas_price = 300"});
|
||||
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"});
|
||||
"owner_reserve = foo",
|
||||
"extension_compute_limit = bar",
|
||||
"extension_size_limit = baz",
|
||||
"gas_price = qux"});
|
||||
// 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"});
|
||||
"owner_reserve = -1234",
|
||||
"extension_compute_limit = -100",
|
||||
"extension_size_limit = -200",
|
||||
"gas_price = -300"});
|
||||
// Illegal values are ignored, and the defaults left unchanged
|
||||
auto setup = setup_FeeVote(config);
|
||||
BEAST_EXPECT(setup.reference_fee == defaultSetup.reference_fee);
|
||||
@@ -74,6 +100,12 @@ 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(
|
||||
@@ -84,12 +116,22 @@ class FeeVote_test : public beast::unit_test::suite
|
||||
config.append(
|
||||
{"reference_fee = " + big64,
|
||||
"account_reserve = " + big64,
|
||||
"owner_reserve = " + big64});
|
||||
"owner_reserve = " + big64,
|
||||
"extension_compute_limit = " + big64,
|
||||
"extension_size_limit = " + big64,
|
||||
"gas_price = " + 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,12 @@ 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) {
|
||||
@@ -116,9 +122,10 @@ 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();
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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
|
||||
|
||||
// TODO: consider moving these to separate files (and figure out the build)
|
||||
|
||||
#include <string>
|
||||
|
||||
extern std::string const ledgerSqnHex;
|
||||
|
||||
extern std::string const allHostFunctionsHex;
|
||||
|
||||
extern std::string const deepRecursionHex;
|
||||
|
||||
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;
|
||||
|
||||
extern std::string const reqNonexistentField;
|
||||
|
||||
extern std::string const hfPerfTest;
|
||||
|
||||
extern std::string const opcCallPerfTest;
|
||||
@@ -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";
|
||||
};
|
||||
|
||||
|
||||
@@ -220,9 +220,8 @@ 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();
|
||||
@@ -257,15 +256,14 @@ 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();
|
||||
};
|
||||
|
||||
@@ -175,6 +175,76 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
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_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
@@ -33,9 +33,14 @@ 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;
|
||||
|
||||
@@ -897,9 +897,8 @@ 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)
|
||||
<< " ";
|
||||
|
||||
@@ -522,6 +522,22 @@ public:
|
||||
if (jv.isMember(jss::reserve_inc) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (env.closed()->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (jv.isMember(jss::extension_compute) != isFlagLedger)
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::extension_size) != isFlagLedger)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (jv.isMember(jss::extension_compute))
|
||||
return false;
|
||||
|
||||
if (jv.isMember(jss::extension_size))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -1578,14 +1594,14 @@ public:
|
||||
{
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{supported_amendments()};
|
||||
FeatureBitset const xrpFees{featureXRPFees};
|
||||
|
||||
testServer();
|
||||
testLedger();
|
||||
testTransactions_APIv1();
|
||||
testTransactions_APIv2();
|
||||
testManifests();
|
||||
testValidations(all - xrpFees);
|
||||
testValidations(all - featureXRPFees - featureSmartEscrow);
|
||||
testValidations(all - featureSmartEscrow);
|
||||
testValidations(all);
|
||||
testSubErrors(true);
|
||||
testSubErrors(false);
|
||||
|
||||
@@ -221,6 +221,15 @@ Ledger::Ledger(
|
||||
sle->at(sfReserveIncrement) = *f;
|
||||
sle->at(sfReferenceFeeUnits) = Config::FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (std::find(
|
||||
amendments.begin(), amendments.end(), featureSmartEscrow) !=
|
||||
amendments.end())
|
||||
{
|
||||
sle->at(sfExtensionComputeLimit) =
|
||||
config.FEES.extension_compute_limit;
|
||||
sle->at(sfExtensionSizeLimit) = config.FEES.extension_size_limit;
|
||||
sle->at(sfGasPrice) = config.FEES.gas_price;
|
||||
}
|
||||
rawInsert(sle);
|
||||
}
|
||||
|
||||
@@ -612,6 +621,7 @@ Ledger::setup()
|
||||
{
|
||||
bool oldFees = false;
|
||||
bool newFees = false;
|
||||
bool extensionFees = false;
|
||||
{
|
||||
auto const baseFee = sle->at(~sfBaseFee);
|
||||
auto const reserveBase = sle->at(~sfReserveBase);
|
||||
@@ -629,6 +639,7 @@ Ledger::setup()
|
||||
auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops);
|
||||
auto const reserveIncrementXRP =
|
||||
sle->at(~sfReserveIncrementDrops);
|
||||
|
||||
auto assign = [&ret](
|
||||
XRPAmount& dest,
|
||||
std::optional<STAmount> const& src) {
|
||||
@@ -645,12 +656,35 @@ Ledger::setup()
|
||||
assign(fees_.increment, reserveIncrementXRP);
|
||||
newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP;
|
||||
}
|
||||
{
|
||||
auto const extensionComputeLimit =
|
||||
sle->at(~sfExtensionComputeLimit);
|
||||
auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit);
|
||||
auto const gasPrice = sle->at(~sfGasPrice);
|
||||
|
||||
auto assign = [](std::uint32_t& dest,
|
||||
std::optional<std::uint32_t> const& src) {
|
||||
if (src)
|
||||
{
|
||||
dest = src.value();
|
||||
}
|
||||
};
|
||||
assign(fees_.extensionComputeLimit, extensionComputeLimit);
|
||||
assign(fees_.extensionSizeLimit, extensionSizeLimit);
|
||||
assign(fees_.gasPrice, gasPrice);
|
||||
extensionFees =
|
||||
extensionComputeLimit || extensionSizeLimit || gasPrice;
|
||||
}
|
||||
if (oldFees && newFees)
|
||||
// Should be all of one or the other, but not both
|
||||
ret = false;
|
||||
if (!rules_.enabled(featureXRPFees) && newFees)
|
||||
// Can't populate the new fees before the amendment is enabled
|
||||
ret = false;
|
||||
if (!rules_.enabled(featureSmartEscrow) && extensionFees)
|
||||
// Can't populate the extension fees before the amendment is
|
||||
// enabled
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
catch (SHAMapMissingNode const&)
|
||||
@@ -669,15 +703,20 @@ Ledger::setup()
|
||||
void
|
||||
Ledger::defaultFees(Config const& config)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0,
|
||||
"ripple::Ledger::defaultFees : zero fees");
|
||||
assert(
|
||||
fees_.base == 0 && fees_.reserve == 0 && fees_.increment == 0 &&
|
||||
fees_.extensionComputeLimit == 0 && fees_.extensionSizeLimit == 0 &&
|
||||
fees_.gasPrice == 0);
|
||||
if (fees_.base == 0)
|
||||
fees_.base = config.FEES.reference_fee;
|
||||
if (fees_.reserve == 0)
|
||||
fees_.reserve = config.FEES.account_reserve;
|
||||
if (fees_.increment == 0)
|
||||
fees_.increment = config.FEES.owner_reserve;
|
||||
if (fees_.extensionComputeLimit == 0)
|
||||
fees_.extensionComputeLimit = config.FEES.extension_compute_limit;
|
||||
if (fees_.extensionSizeLimit == 0)
|
||||
fees_.extensionSizeLimit = config.FEES.extension_size_limit;
|
||||
if (fees_.gasPrice == 0)
|
||||
fees_.gasPrice = config.FEES.gas_price;
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE>
|
||||
|
||||
@@ -225,30 +225,30 @@ log_metadata_difference(
|
||||
{
|
||||
JLOG(j.debug()) << "MISMATCH on TX " << tx
|
||||
<< ": Different result and index!";
|
||||
JLOG(j.debug()) << " Built:"
|
||||
<< " Result: " << builtMetaData->getResult()
|
||||
<< " Index: " << builtMetaData->getIndex();
|
||||
JLOG(j.debug()) << " Valid:"
|
||||
<< " Result: " << validMetaData->getResult()
|
||||
<< " Index: " << validMetaData->getIndex();
|
||||
JLOG(j.debug())
|
||||
<< " Built:" << " Result: " << builtMetaData->getResult()
|
||||
<< " Index: " << builtMetaData->getIndex();
|
||||
JLOG(j.debug())
|
||||
<< " Valid:" << " Result: " << validMetaData->getResult()
|
||||
<< " Index: " << validMetaData->getIndex();
|
||||
}
|
||||
else if (result_diff)
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "MISMATCH on TX " << tx << ": Different result!";
|
||||
JLOG(j.debug()) << " Built:"
|
||||
<< " Result: " << builtMetaData->getResult();
|
||||
JLOG(j.debug()) << " Valid:"
|
||||
<< " Result: " << validMetaData->getResult();
|
||||
JLOG(j.debug())
|
||||
<< " Built:" << " Result: " << builtMetaData->getResult();
|
||||
JLOG(j.debug())
|
||||
<< " Valid:" << " Result: " << validMetaData->getResult();
|
||||
}
|
||||
else if (index_diff)
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "MISMATCH on TX " << tx << ": Different index!";
|
||||
JLOG(j.debug()) << " Built:"
|
||||
<< " Index: " << builtMetaData->getIndex();
|
||||
JLOG(j.debug()) << " Valid:"
|
||||
<< " Index: " << validMetaData->getIndex();
|
||||
JLOG(j.debug())
|
||||
<< " Built:" << " Index: " << builtMetaData->getIndex();
|
||||
JLOG(j.debug())
|
||||
<< " Valid:" << " Index: " << validMetaData->getIndex();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -267,12 +267,12 @@ log_metadata_difference(
|
||||
JLOG(j.debug()) << "MISMATCH on TX " << tx
|
||||
<< ": Different result and nodes!";
|
||||
JLOG(j.debug())
|
||||
<< " Built:"
|
||||
<< " Result: " << builtMetaData->getResult() << " Nodes:\n"
|
||||
<< " Built:" << " Result: " << builtMetaData->getResult()
|
||||
<< " Nodes:\n"
|
||||
<< builtNodes.getJson(JsonOptions::none);
|
||||
JLOG(j.debug())
|
||||
<< " Valid:"
|
||||
<< " Result: " << validMetaData->getResult() << " Nodes:\n"
|
||||
<< " Valid:" << " Result: " << validMetaData->getResult()
|
||||
<< " Nodes:\n"
|
||||
<< validNodes.getJson(JsonOptions::none);
|
||||
}
|
||||
else if (index_diff)
|
||||
@@ -280,23 +280,21 @@ log_metadata_difference(
|
||||
JLOG(j.debug()) << "MISMATCH on TX " << tx
|
||||
<< ": Different index and nodes!";
|
||||
JLOG(j.debug())
|
||||
<< " Built:"
|
||||
<< " Index: " << builtMetaData->getIndex() << " Nodes:\n"
|
||||
<< " Built:" << " Index: " << builtMetaData->getIndex()
|
||||
<< " Nodes:\n"
|
||||
<< builtNodes.getJson(JsonOptions::none);
|
||||
JLOG(j.debug())
|
||||
<< " Valid:"
|
||||
<< " Index: " << validMetaData->getIndex() << " Nodes:\n"
|
||||
<< " Valid:" << " Index: " << validMetaData->getIndex()
|
||||
<< " Nodes:\n"
|
||||
<< validNodes.getJson(JsonOptions::none);
|
||||
}
|
||||
else // nodes_diff
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "MISMATCH on TX " << tx << ": Different nodes!";
|
||||
JLOG(j.debug()) << " Built:"
|
||||
<< " Nodes:\n"
|
||||
JLOG(j.debug()) << " Built:" << " Nodes:\n"
|
||||
<< builtNodes.getJson(JsonOptions::none);
|
||||
JLOG(j.debug()) << " Valid:"
|
||||
<< " Nodes:\n"
|
||||
JLOG(j.debug()) << " Valid:" << " Nodes:\n"
|
||||
<< validNodes.getJson(JsonOptions::none);
|
||||
}
|
||||
}
|
||||
@@ -353,10 +351,10 @@ LedgerHistory::handleMismatch(
|
||||
|
||||
if (!builtLedger || !validLedger)
|
||||
{
|
||||
JLOG(j_.error()) << "MISMATCH cannot be analyzed:"
|
||||
<< " builtLedger: " << to_string(built) << " -> "
|
||||
<< builtLedger << " validLedger: " << to_string(valid)
|
||||
<< " -> " << validLedger;
|
||||
JLOG(j_.error()) << "MISMATCH cannot be analyzed:" << " builtLedger: "
|
||||
<< to_string(built) << " -> " << builtLedger
|
||||
<< " validLedger: " << to_string(valid) << " -> "
|
||||
<< validLedger;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -100,8 +100,8 @@ TimeoutCounter::invokeOnTimer()
|
||||
if (!progress_)
|
||||
{
|
||||
++timeouts_;
|
||||
JLOG(journal_.debug()) << "Timeout(" << timeouts_ << ") "
|
||||
<< " acquiring " << hash_;
|
||||
JLOG(journal_.debug())
|
||||
<< "Timeout(" << timeouts_ << ") " << " acquiring " << hash_;
|
||||
onTimer(false, sl);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -447,8 +447,8 @@ GRPCServerImpl::handleRpcs()
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
JLOG(journal_.debug()) << "Request listener cancelled. "
|
||||
<< "Destroying object";
|
||||
JLOG(journal_.debug())
|
||||
<< "Request listener cancelled. " << "Destroying object";
|
||||
erase(ptr);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -384,9 +384,9 @@ changeSpotPriceQuality(
|
||||
{
|
||||
JLOG(j.error())
|
||||
<< "changeSpotPriceQuality failed: " << to_string(pool.in)
|
||||
<< " " << to_string(pool.out) << " "
|
||||
<< " " << quality << " " << tfee << " "
|
||||
<< to_string(amounts.in) << " " << to_string(amounts.out);
|
||||
<< " " << to_string(pool.out) << " " << " " << quality
|
||||
<< " " << tfee << " " << to_string(amounts.in) << " "
|
||||
<< to_string(amounts.out);
|
||||
Throw<std::runtime_error>("changeSpotPriceQuality failed");
|
||||
}
|
||||
else
|
||||
|
||||
@@ -28,10 +28,10 @@ namespace ripple {
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename value_type>
|
||||
class VotableValue
|
||||
{
|
||||
private:
|
||||
using value_type = XRPAmount;
|
||||
value_type const current_; // The current setting
|
||||
value_type const target_; // The setting we want
|
||||
std::map<value_type, int> voteMap_;
|
||||
@@ -66,8 +66,9 @@ public:
|
||||
getVotes() const;
|
||||
};
|
||||
|
||||
auto
|
||||
VotableValue::getVotes() const -> std::pair<value_type, bool>
|
||||
template <typename value_type>
|
||||
std::pair<value_type, bool>
|
||||
VotableValue<value_type>::getVotes() const
|
||||
{
|
||||
value_type ourVote = current_;
|
||||
int weight = 0;
|
||||
@@ -190,6 +191,33 @@ FeeVoteImpl::doValidation(
|
||||
"reserve increment",
|
||||
sfReserveIncrement);
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
auto vote = [&v, this](
|
||||
auto const current,
|
||||
std::uint32_t target,
|
||||
char const* name,
|
||||
auto const& sfield) {
|
||||
if (current != target)
|
||||
{
|
||||
JLOG(journal_.info())
|
||||
<< "Voting for " << name << " of " << target;
|
||||
|
||||
v[sfield] = target;
|
||||
}
|
||||
};
|
||||
vote(
|
||||
lastFees.extensionComputeLimit,
|
||||
target_.extension_compute_limit,
|
||||
"extension compute limit",
|
||||
sfExtensionComputeLimit);
|
||||
vote(
|
||||
lastFees.extensionSizeLimit,
|
||||
target_.extension_size_limit,
|
||||
"extension size limit",
|
||||
sfExtensionSizeLimit);
|
||||
vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -212,11 +240,22 @@ FeeVoteImpl::doVoting(
|
||||
detail::VotableValue incReserveVote(
|
||||
lastClosedLedger->fees().increment, target_.owner_reserve);
|
||||
|
||||
detail::VotableValue extensionComputeVote(
|
||||
lastClosedLedger->fees().extensionComputeLimit,
|
||||
target_.extension_compute_limit);
|
||||
|
||||
detail::VotableValue extensionSizeVote(
|
||||
lastClosedLedger->fees().extensionSizeLimit,
|
||||
target_.extension_size_limit);
|
||||
|
||||
detail::VotableValue gasPriceVote(
|
||||
lastClosedLedger->fees().gasPrice, target_.gas_price);
|
||||
|
||||
auto const& rules = lastClosedLedger->rules();
|
||||
if (rules.enabled(featureXRPFees))
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
SF_AMOUNT const& xrpField) {
|
||||
if (auto const field = ~val->at(~xrpField);
|
||||
field && field->native())
|
||||
@@ -245,7 +284,7 @@ FeeVoteImpl::doVoting(
|
||||
else
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue& value,
|
||||
detail::VotableValue<XRPAmount>& value,
|
||||
auto const& valueField) {
|
||||
if (auto const field = val->at(~valueField))
|
||||
{
|
||||
@@ -276,6 +315,30 @@ FeeVoteImpl::doVoting(
|
||||
doVote(val, incReserveVote, sfReserveIncrement);
|
||||
}
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
auto doVote = [](std::shared_ptr<STValidation> const& val,
|
||||
detail::VotableValue<std::uint32_t>& value,
|
||||
SF_UINT32 const& extensionField) {
|
||||
if (auto const field = ~val->at(~extensionField); field)
|
||||
{
|
||||
value.addVote(field.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
value.noVote();
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& val : set)
|
||||
{
|
||||
if (!val->isTrusted())
|
||||
continue;
|
||||
doVote(val, extensionComputeVote, sfExtensionComputeLimit);
|
||||
doVote(val, extensionSizeVote, sfExtensionSizeLimit);
|
||||
doVote(val, gasPriceVote, sfGasPrice);
|
||||
}
|
||||
}
|
||||
|
||||
// choose our positions
|
||||
// TODO: Use structured binding once LLVM 16 is the minimum supported
|
||||
@@ -284,11 +347,15 @@ FeeVoteImpl::doVoting(
|
||||
auto const baseFee = baseFeeVote.getVotes();
|
||||
auto const baseReserve = baseReserveVote.getVotes();
|
||||
auto const incReserve = incReserveVote.getVotes();
|
||||
auto const extensionCompute = extensionComputeVote.getVotes();
|
||||
auto const extensionSize = extensionSizeVote.getVotes();
|
||||
auto const gasPrice = gasPriceVote.getVotes();
|
||||
|
||||
auto const seq = lastClosedLedger->info().seq + 1;
|
||||
|
||||
// add transactions to our position
|
||||
if (baseFee.second || baseReserve.second || incReserve.second)
|
||||
if (baseFee.second || baseReserve.second || incReserve.second ||
|
||||
extensionCompute.second || extensionSize.second || gasPrice.second)
|
||||
{
|
||||
JLOG(journal_.warn())
|
||||
<< "We are voting for a fee change: " << baseFee.first << "/"
|
||||
@@ -316,6 +383,12 @@ FeeVoteImpl::doVoting(
|
||||
incReserveVote.current());
|
||||
obj[sfReferenceFeeUnits] = Config::FEE_UNITS_DEPRECATED;
|
||||
}
|
||||
if (rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
obj[sfExtensionComputeLimit] = extensionCompute.first;
|
||||
obj[sfExtensionSizeLimit] = extensionSize.first;
|
||||
obj[sfGasPrice] = gasPrice.first;
|
||||
}
|
||||
});
|
||||
|
||||
uint256 txID = feeTx.getTransactionID();
|
||||
|
||||
@@ -2467,6 +2467,18 @@ NetworkOPsImp::pubValidation(std::shared_ptr<STValidation> const& val)
|
||||
reserveIncXRP && reserveIncXRP->native())
|
||||
jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped();
|
||||
|
||||
if (auto const extensionComputeLimit =
|
||||
~val->at(~sfExtensionComputeLimit);
|
||||
extensionComputeLimit)
|
||||
jvObj[jss::extension_compute] = *extensionComputeLimit;
|
||||
|
||||
if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit);
|
||||
extensionSizeLimit)
|
||||
jvObj[jss::extension_size] = *extensionSizeLimit;
|
||||
|
||||
if (auto const gasPrice = ~val->at(~sfGasPrice); gasPrice)
|
||||
jvObj[jss::gas_price] = *gasPrice;
|
||||
|
||||
// NOTE Use MultiApiJson to publish two slightly different JSON objects
|
||||
// for consumers supporting different API versions
|
||||
MultiApiJson multiObj{jvObj};
|
||||
@@ -2916,12 +2928,22 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
|
||||
l[jss::seq] = Json::UInt(lpClosed->info().seq);
|
||||
l[jss::hash] = to_string(lpClosed->info().hash);
|
||||
|
||||
bool const smartEscrowEnabled =
|
||||
m_ledgerMaster.getValidatedLedger()->rules().enabled(
|
||||
featureSmartEscrow);
|
||||
if (!human)
|
||||
{
|
||||
l[jss::base_fee] = baseFee.jsonClipped();
|
||||
l[jss::reserve_base] =
|
||||
lpClosed->fees().accountReserve(0).jsonClipped();
|
||||
l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
|
||||
if (smartEscrowEnabled)
|
||||
{
|
||||
l[jss::extension_compute] =
|
||||
lpClosed->fees().extensionComputeLimit;
|
||||
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
|
||||
l[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
l[jss::close_time] = Json::Value::UInt(
|
||||
lpClosed->info().closeTime.time_since_epoch().count());
|
||||
}
|
||||
@@ -2931,6 +2953,13 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters)
|
||||
l[jss::reserve_base_xrp] =
|
||||
lpClosed->fees().accountReserve(0).decimalXRP();
|
||||
l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP();
|
||||
if (smartEscrowEnabled)
|
||||
{
|
||||
l[jss::extension_compute] =
|
||||
lpClosed->fees().extensionComputeLimit;
|
||||
l[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
|
||||
l[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
|
||||
if (auto const closeOffset = app_.timeKeeper().closeOffset();
|
||||
std::abs(closeOffset.count()) >= 60)
|
||||
@@ -3121,6 +3150,14 @@ NetworkOPsImp::pubLedger(std::shared_ptr<ReadView const> const& lpAccepted)
|
||||
lpAccepted->fees().accountReserve(0).jsonClipped();
|
||||
jvObj[jss::reserve_inc] =
|
||||
lpAccepted->fees().increment.jsonClipped();
|
||||
if (lpAccepted->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
jvObj[jss::extension_compute] =
|
||||
lpAccepted->fees().extensionComputeLimit;
|
||||
jvObj[jss::extension_size] =
|
||||
lpAccepted->fees().extensionSizeLimit;
|
||||
jvObj[jss::gas_price] = lpAccepted->fees().gasPrice;
|
||||
}
|
||||
|
||||
jvObj[jss::txn_count] = Json::UInt(alpAccepted->size());
|
||||
|
||||
@@ -3491,8 +3528,8 @@ NetworkOPsImp::pubAccountTransaction(
|
||||
}
|
||||
|
||||
JLOG(m_journal.trace())
|
||||
<< "pubAccountTransaction: "
|
||||
<< "proposed=" << iProposed << ", accepted=" << iAccepted;
|
||||
<< "pubAccountTransaction: " << "proposed=" << iProposed
|
||||
<< ", accepted=" << iAccepted;
|
||||
|
||||
if (!notify.empty() || !accountHistoryNotify.empty())
|
||||
{
|
||||
@@ -4172,6 +4209,13 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, Json::Value& jvResult)
|
||||
jvResult[jss::reserve_base] =
|
||||
lpClosed->fees().accountReserve(0).jsonClipped();
|
||||
jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped();
|
||||
if (lpClosed->rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
jvResult[jss::extension_compute] =
|
||||
lpClosed->fees().extensionComputeLimit;
|
||||
jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit;
|
||||
jvResult[jss::gas_price] = lpClosed->fees().gasPrice;
|
||||
}
|
||||
}
|
||||
|
||||
if ((mMode >= OperatingMode::SYNCING) && !isNeedNetworkLedger())
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,327 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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 <xrpld/app/misc/WasmVM.h>
|
||||
|
||||
#include <wasm_c_api.h>
|
||||
#include <wasm_export.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct WamrResult
|
||||
{
|
||||
wasm_val_vec_t r;
|
||||
bool f; // failure flag
|
||||
|
||||
WamrResult(unsigned N = 0) : r{0, nullptr, 0, 0, nullptr}, f(false)
|
||||
{
|
||||
if (N)
|
||||
wasm_val_vec_new_uninitialized(&r, N);
|
||||
}
|
||||
|
||||
~WamrResult()
|
||||
{
|
||||
if (r.size)
|
||||
wasm_val_vec_delete(&r);
|
||||
}
|
||||
|
||||
WamrResult(WamrResult const&) = delete;
|
||||
WamrResult&
|
||||
operator=(WamrResult const&) = delete;
|
||||
|
||||
WamrResult(WamrResult&& o)
|
||||
{
|
||||
*this = std::move(o);
|
||||
}
|
||||
|
||||
WamrResult&
|
||||
operator=(WamrResult&& o)
|
||||
{
|
||||
r = o.r;
|
||||
o.r = {0, nullptr, 0, 0, nullptr};
|
||||
f = o.f;
|
||||
o.f = false;
|
||||
return *this;
|
||||
}
|
||||
// operator wasm_val_vec_t &() {return r;}
|
||||
};
|
||||
|
||||
using ModulePtr = std::unique_ptr<wasm_module_t, decltype(&wasm_module_delete)>;
|
||||
using InstancePtr =
|
||||
std::unique_ptr<wasm_instance_t, decltype(&wasm_instance_delete)>;
|
||||
|
||||
using FuncInfo = std::pair<wasm_func_t const*, wasm_functype_t const*>;
|
||||
|
||||
struct InstanceWrapper
|
||||
{
|
||||
wasm_extern_vec_t exports_;
|
||||
InstancePtr instance_;
|
||||
wasm_exec_env_t execEnv_ = nullptr;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
private:
|
||||
static InstancePtr
|
||||
init(
|
||||
wasm_store_t* s,
|
||||
wasm_module_t* m,
|
||||
int32_t maxPages,
|
||||
wasm_extern_vec_t* expt,
|
||||
wasm_extern_vec_t const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
public:
|
||||
InstanceWrapper();
|
||||
|
||||
InstanceWrapper(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper&
|
||||
operator=(InstanceWrapper&& o);
|
||||
|
||||
InstanceWrapper(
|
||||
wasm_store_t* s,
|
||||
wasm_module_t* m,
|
||||
int32_t maxPages,
|
||||
int64_t gas,
|
||||
wasm_extern_vec_t const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
~InstanceWrapper();
|
||||
|
||||
operator bool() const;
|
||||
|
||||
FuncInfo
|
||||
getFunc(
|
||||
std::string_view funcName,
|
||||
wasm_exporttype_vec_t const& export_types) const;
|
||||
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
std::int64_t
|
||||
getGas() const;
|
||||
};
|
||||
|
||||
struct ModuleWrapper
|
||||
{
|
||||
ModulePtr module_;
|
||||
InstanceWrapper instanceWrap_;
|
||||
wasm_exporttype_vec_t exportTypes_;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
private:
|
||||
static ModulePtr
|
||||
init(wasm_store_t* s, Bytes const& wasmBin, beast::Journal j);
|
||||
|
||||
public:
|
||||
ModuleWrapper();
|
||||
ModuleWrapper(ModuleWrapper&& o);
|
||||
ModuleWrapper&
|
||||
operator=(ModuleWrapper&& o);
|
||||
ModuleWrapper(
|
||||
wasm_store_t* s,
|
||||
Bytes const& wasmBin,
|
||||
bool instantiate,
|
||||
int32_t maxPages,
|
||||
int64_t gas,
|
||||
std::vector<WasmImportFunc> const& imports,
|
||||
beast::Journal j);
|
||||
~ModuleWrapper();
|
||||
|
||||
operator bool() const;
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName) const;
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
InstanceWrapper const&
|
||||
getInstance(int i = 0) const;
|
||||
|
||||
int
|
||||
addInstance(
|
||||
wasm_store_t* s,
|
||||
int32_t maxPages,
|
||||
int64_t gas,
|
||||
wasm_extern_vec_t const& imports = WASM_EMPTY_VEC);
|
||||
|
||||
std::int64_t
|
||||
getGas();
|
||||
|
||||
private:
|
||||
static void
|
||||
makeImpParams(wasm_valtype_vec_t& v, WasmImportFunc const& imp);
|
||||
static void
|
||||
makeImpReturn(wasm_valtype_vec_t& v, WasmImportFunc const& imp);
|
||||
wasm_extern_vec_t
|
||||
buildImports(wasm_store_t* s, std::vector<WasmImportFunc> const& imports);
|
||||
};
|
||||
|
||||
class WamrEngine
|
||||
{
|
||||
std::unique_ptr<wasm_engine_t, decltype(&wasm_engine_delete)> engine_;
|
||||
std::unique_ptr<wasm_store_t, decltype(&wasm_store_delete)> store_;
|
||||
std::unique_ptr<ModuleWrapper> moduleWrap_;
|
||||
std::int32_t defMaxPages_ = -1;
|
||||
beast::Journal j_ = beast::Journal(beast::Journal::getNullSink());
|
||||
|
||||
std::mutex m_; // 1 instance mutex
|
||||
|
||||
public:
|
||||
WamrEngine();
|
||||
~WamrEngine() = default;
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
run(Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
std::vector<WasmImportFunc> const& imports,
|
||||
HostFunctions* hfs,
|
||||
int64_t gas,
|
||||
beast::Journal j);
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
std::vector<WasmImportFunc> const& imports,
|
||||
beast::Journal j);
|
||||
|
||||
std::int32_t
|
||||
initMaxPages(std::int32_t def);
|
||||
|
||||
std::int64_t
|
||||
getGas();
|
||||
|
||||
// Host functions helper functionality
|
||||
wasm_trap_t*
|
||||
newTrap(std::string_view msg);
|
||||
|
||||
beast::Journal
|
||||
getJournal() const;
|
||||
|
||||
private:
|
||||
InstanceWrapper const&
|
||||
getRT(int m = 0, int i = 0);
|
||||
|
||||
wmem
|
||||
getMem() const;
|
||||
|
||||
int32_t
|
||||
allocate(int32_t size);
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
runHlp(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
std::vector<WasmImportFunc> const& imports,
|
||||
HostFunctions* hfs,
|
||||
int64_t gas);
|
||||
|
||||
NotTEC
|
||||
checkHlp(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
std::vector<WasmImportFunc> const& imports);
|
||||
|
||||
int
|
||||
addModule(
|
||||
Bytes const& wasmCode,
|
||||
bool instantiate,
|
||||
int64_t gas,
|
||||
std::vector<WasmImportFunc> const& imports);
|
||||
void
|
||||
clearModules();
|
||||
|
||||
// int addInstance();
|
||||
|
||||
int32_t
|
||||
runFunc(std::string_view const funcName, int32_t p);
|
||||
|
||||
int32_t
|
||||
makeModule(
|
||||
Bytes const& wasmCode,
|
||||
wasm_extern_vec_t const& imports = WASM_EMPTY_VEC);
|
||||
|
||||
FuncInfo
|
||||
getFunc(std::string_view funcName);
|
||||
|
||||
std::vector<wasm_val_t>
|
||||
convertParams(std::vector<WasmParam> const& params);
|
||||
|
||||
static int
|
||||
compareParamTypes(
|
||||
wasm_valtype_vec_t const* ftp,
|
||||
std::vector<wasm_val_t> const& p);
|
||||
|
||||
static void
|
||||
add_param(std::vector<wasm_val_t>& in, int32_t p);
|
||||
static void
|
||||
add_param(std::vector<wasm_val_t>& in, int64_t p);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(std::string_view func, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(FuncInfo const& f, Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(FuncInfo const& f, std::vector<wasm_val_t>& in);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
std::int32_t p,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
std::int64_t p,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
uint8_t const* d,
|
||||
std::size_t sz,
|
||||
Types&&... args);
|
||||
|
||||
template <int NR, class... Types>
|
||||
inline WamrResult
|
||||
call(
|
||||
FuncInfo const& f,
|
||||
std::vector<wasm_val_t>& in,
|
||||
Bytes const& p,
|
||||
Types&&... args);
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,228 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpld/app/misc/WasmParamsHelper.h>
|
||||
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum HostFunctionErrors : int32_t {
|
||||
HF_ERR_INTERNAL = -1,
|
||||
HF_ERR_FIELD_NOT_FOUND = -2,
|
||||
HF_ERR_BUFFER_TOO_SMALL = -3,
|
||||
HF_ERR_NO_ARRAY = -4,
|
||||
HF_ERR_NOT_LEAF_FIELD = -5,
|
||||
HF_ERR_LOCATOR_MALFORMED = -6,
|
||||
HF_ERR_SLOT_OUT_RANGE = -7,
|
||||
HF_ERR_SLOTS_FULL = -8,
|
||||
HF_ERR_EMPTY_SLOT = -9,
|
||||
HF_ERR_LEDGER_OBJ_NOT_FOUND = -10,
|
||||
HF_ERR_DECODING = -11,
|
||||
HF_ERR_DATA_FIELD_TOO_LARGE = -12,
|
||||
HF_ERR_POINTER_OUT_OF_BOUNDS = -13,
|
||||
HF_ERR_NO_MEM_EXPORTED = -14,
|
||||
HF_ERR_INVALID_PARAMS = -15,
|
||||
HF_ERR_INVALID_ACCOUNT = -16,
|
||||
HF_ERR_INVALID_FIELD = -17,
|
||||
HF_ERR_INDEX_OUT_OF_BOUNDS = -18,
|
||||
};
|
||||
|
||||
struct HostFunctions
|
||||
{
|
||||
virtual void
|
||||
setRT(void const*)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void const*
|
||||
getRT() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
virtual beast::Journal
|
||||
getJournal()
|
||||
{
|
||||
return beast::Journal{beast::Journal::getNullSink()};
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getLedgerSqn()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getParentLedgerTime()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual Hash
|
||||
getParentLedgerHash()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getTxField(SField const& fname)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getCurrentLedgerObjField(SField const& fname)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getTxNestedField(Slice const& locator)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getCurrentLedgerObjNestedField(Slice const& locator)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getTxArrayLen(SField const& fname)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getCurrentLedgerObjArrayLen(SField const& fname)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getTxNestedArrayLen(Slice const& locator)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getCurrentLedgerObjNestedArrayLen(Slice const& locator)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
updateData(Bytes const& data)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual Hash
|
||||
computeSha512HalfHash(Bytes const& data)
|
||||
{
|
||||
return Hash{};
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
accountKeylet(AccountID const& account)
|
||||
{
|
||||
return Bytes{};
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
credentialKeylet(
|
||||
AccountID const& subject,
|
||||
AccountID const& issuer,
|
||||
Bytes const& credentialType)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t docId)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual Expected<Bytes, int32_t>
|
||||
getNFT(AccountID const& account, uint256 const& nftId)
|
||||
{
|
||||
return Unexpected(HF_ERR_INTERNAL);
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
trace(std::string const& msg, Bytes const& data, bool asHex)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual int32_t
|
||||
traceNum(std::string const& msg, int64_t data)
|
||||
{
|
||||
return HF_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
virtual ~HostFunctions() = default;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,515 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpld/app/misc/WasmHostFuncImpl.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
|
||||
#include <xrpl/protocol/STBitString.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
// #define DEBUG_OUTPUT 1
|
||||
// #define DEBUG_OUTPUT_WAMR 1
|
||||
#endif
|
||||
|
||||
namespace ripple {
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getLedgerSqn()
|
||||
{
|
||||
return ctx.view().seq();
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getParentLedgerTime()
|
||||
{
|
||||
return ctx.view().parentCloseTime().time_since_epoch().count();
|
||||
}
|
||||
|
||||
Hash
|
||||
WasmHostFunctionsImpl::getParentLedgerHash()
|
||||
{
|
||||
return ctx.view().info().parentHash;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx)
|
||||
{
|
||||
if (cacheIdx < 0 || cacheIdx > MAX_CACHE)
|
||||
return HF_ERR_SLOT_OUT_RANGE;
|
||||
|
||||
if (!cacheIdx)
|
||||
{
|
||||
for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx)
|
||||
if (!cache[cacheIdx])
|
||||
break;
|
||||
}
|
||||
else
|
||||
--cacheIdx;
|
||||
|
||||
if (cacheIdx >= MAX_CACHE)
|
||||
return HF_ERR_SLOTS_FULL;
|
||||
|
||||
cache[cacheIdx] = ctx.view().read(keylet);
|
||||
return cache[cacheIdx] ? cacheIdx + 1 : HF_ERR_LEDGER_OBJ_NOT_FOUND;
|
||||
}
|
||||
|
||||
static Expected<Bytes, int32_t>
|
||||
getAnyFieldData(STBase const* obj)
|
||||
{
|
||||
// auto const& fname = obj.getFName();
|
||||
if (!obj)
|
||||
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
|
||||
|
||||
auto const stype = obj->getSType();
|
||||
switch (stype)
|
||||
{
|
||||
case STI_UNKNOWN:
|
||||
case STI_NOTPRESENT:
|
||||
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
|
||||
break;
|
||||
case STI_OBJECT:
|
||||
case STI_ARRAY:
|
||||
return Unexpected(HF_ERR_NOT_LEAF_FIELD);
|
||||
break;
|
||||
case STI_ACCOUNT: {
|
||||
auto const& super(static_cast<STAccount const*>(obj));
|
||||
auto const& data = super->value();
|
||||
return Bytes{data.begin(), data.end()};
|
||||
}
|
||||
break;
|
||||
case STI_AMOUNT: {
|
||||
auto const& super(static_cast<STAmount const*>(obj));
|
||||
int64_t const data = super->xrp().drops();
|
||||
auto const* b = reinterpret_cast<uint8_t const*>(&data);
|
||||
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
|
||||
return Bytes{b, e};
|
||||
}
|
||||
break;
|
||||
case STI_VL: {
|
||||
auto const& super(static_cast<STBlob const*>(obj));
|
||||
auto const& data = super->value();
|
||||
return Bytes{data.begin(), data.end()};
|
||||
}
|
||||
break;
|
||||
case STI_UINT256: {
|
||||
auto const& super(static_cast<STBitString<256> const*>(obj));
|
||||
auto const& data = super->value();
|
||||
return Bytes{data.begin(), data.end()};
|
||||
}
|
||||
break;
|
||||
case STI_UINT32: {
|
||||
auto const& super(
|
||||
static_cast<STInteger<std::uint32_t> const*>(obj));
|
||||
std::uint32_t const data = super->value();
|
||||
auto const* b = reinterpret_cast<uint8_t const*>(&data);
|
||||
auto const* e = reinterpret_cast<uint8_t const*>(&data + 1);
|
||||
return Bytes{b, e};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Serializer msg;
|
||||
obj->add(msg);
|
||||
|
||||
return msg.getData();
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getTxField(SField const& fname)
|
||||
{
|
||||
return getAnyFieldData(ctx.tx.peekAtPField(fname));
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname)
|
||||
{
|
||||
auto const sle = ctx.view().read(leKey);
|
||||
if (!sle)
|
||||
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
|
||||
return getAnyFieldData(sle->peekAtPField(fname));
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname)
|
||||
{
|
||||
--cacheIdx;
|
||||
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
|
||||
return Unexpected(HF_ERR_SLOT_OUT_RANGE);
|
||||
if (!cache[cacheIdx])
|
||||
return Unexpected(HF_ERR_EMPTY_SLOT);
|
||||
return getAnyFieldData(cache[cacheIdx]->peekAtPField(fname));
|
||||
}
|
||||
|
||||
static inline bool
|
||||
noField(STBase const* field)
|
||||
{
|
||||
return !field || (STI_NOTPRESENT == field->getSType()) ||
|
||||
(STI_UNKNOWN == field->getSType());
|
||||
}
|
||||
|
||||
static Expected<STBase const*, int32_t>
|
||||
locateField(STObject const& obj, Slice const& loc)
|
||||
{
|
||||
if (loc.empty() || (loc.size() & 3)) // must be multiple of 4
|
||||
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
|
||||
|
||||
int32_t const* l = reinterpret_cast<int32_t const*>(loc.data());
|
||||
int32_t const sz = loc.size() / 4;
|
||||
STBase const* field = nullptr;
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
|
||||
{
|
||||
int32_t const c = l[0];
|
||||
auto const it = m.find(c);
|
||||
if (it == m.end())
|
||||
return Unexpected(HF_ERR_INVALID_FIELD);
|
||||
|
||||
auto const& fname(*it->second);
|
||||
field = obj.peekAtPField(fname);
|
||||
if (noField(field))
|
||||
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
|
||||
}
|
||||
|
||||
for (int i = 1; i < sz; ++i)
|
||||
{
|
||||
int32_t const c = l[i];
|
||||
|
||||
if (STI_ARRAY == field->getSType())
|
||||
{
|
||||
auto const* arr = static_cast<STArray const*>(field);
|
||||
if (c >= arr->size())
|
||||
return Unexpected(HF_ERR_INDEX_OUT_OF_BOUNDS);
|
||||
field = &(arr->operator[](c));
|
||||
}
|
||||
else if (STI_OBJECT == field->getSType())
|
||||
{
|
||||
auto const* o = static_cast<STObject const*>(field);
|
||||
|
||||
auto const it = m.find(c);
|
||||
if (it == m.end())
|
||||
return Unexpected(HF_ERR_INVALID_FIELD);
|
||||
|
||||
auto const& fname(*it->second);
|
||||
field = o->peekAtPField(fname);
|
||||
}
|
||||
else // simple field must be the last one
|
||||
{
|
||||
return Unexpected(HF_ERR_LOCATOR_MALFORMED);
|
||||
}
|
||||
|
||||
if (noField(field))
|
||||
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getTxNestedField(Slice const& locator)
|
||||
{
|
||||
auto const r = locateField(ctx.tx, locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
return getAnyFieldData(r.value());
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(Slice const& locator)
|
||||
{
|
||||
auto const sle = ctx.view().read(leKey);
|
||||
if (!sle)
|
||||
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
|
||||
|
||||
auto const r = locateField(*sle, locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
return getAnyFieldData(r.value());
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getLedgerObjNestedField(
|
||||
int32_t cacheIdx,
|
||||
Slice const& locator)
|
||||
{
|
||||
--cacheIdx;
|
||||
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
|
||||
return Unexpected(HF_ERR_SLOT_OUT_RANGE);
|
||||
|
||||
if (!cache[cacheIdx])
|
||||
return Unexpected(HF_ERR_EMPTY_SLOT);
|
||||
|
||||
auto const r = locateField(*cache[cacheIdx], locator);
|
||||
if (!r)
|
||||
return Unexpected(r.error());
|
||||
|
||||
return getAnyFieldData(r.value());
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getTxArrayLen(SField const& fname)
|
||||
{
|
||||
if (fname.fieldType != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
|
||||
auto const* field = ctx.tx.peekAtPField(fname);
|
||||
if (noField(field))
|
||||
return HF_ERR_FIELD_NOT_FOUND;
|
||||
|
||||
if (field->getSType() != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
int32_t const sz = static_cast<STArray const*>(field)->size();
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname)
|
||||
{
|
||||
if (fname.fieldType != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
|
||||
auto const sle = ctx.view().read(leKey);
|
||||
if (!sle)
|
||||
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
|
||||
|
||||
auto const* field = sle->peekAtPField(fname);
|
||||
if (noField(field))
|
||||
return HF_ERR_FIELD_NOT_FOUND;
|
||||
|
||||
if (field->getSType() != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
int32_t const sz = static_cast<STArray const*>(field)->size();
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getLedgerObjArrayLen(
|
||||
int32_t cacheIdx,
|
||||
SField const& fname)
|
||||
{
|
||||
if (fname.fieldType != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
|
||||
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
|
||||
return HF_ERR_SLOT_OUT_RANGE;
|
||||
|
||||
if (!cache[cacheIdx])
|
||||
return HF_ERR_EMPTY_SLOT;
|
||||
|
||||
auto const* field = cache[cacheIdx]->peekAtPField(fname);
|
||||
if (noField(field))
|
||||
return HF_ERR_FIELD_NOT_FOUND;
|
||||
|
||||
if (field->getSType() != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
int32_t const sz = static_cast<STArray const*>(field)->size();
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator)
|
||||
{
|
||||
auto const r = locateField(ctx.tx, locator);
|
||||
if (!r)
|
||||
return r.error();
|
||||
|
||||
auto const* field = r.value();
|
||||
if (field->getSType() != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
int32_t const sz = static_cast<STArray const*>(field)->size();
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator)
|
||||
{
|
||||
auto const sle = ctx.view().read(leKey);
|
||||
if (!sle)
|
||||
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
|
||||
auto const r = locateField(*sle, locator);
|
||||
if (!r)
|
||||
return r.error();
|
||||
|
||||
auto const* field = r.value();
|
||||
if (field->getSType() != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
int32_t const sz = static_cast<STArray const*>(field)->size();
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(
|
||||
int32_t cacheIdx,
|
||||
Slice const& locator)
|
||||
{
|
||||
--cacheIdx;
|
||||
if (cacheIdx < 0 || cacheIdx >= MAX_CACHE)
|
||||
return HF_ERR_SLOT_OUT_RANGE;
|
||||
|
||||
if (!cache[cacheIdx])
|
||||
return HF_ERR_EMPTY_SLOT;
|
||||
|
||||
auto const r = locateField(*cache[cacheIdx], locator);
|
||||
if (!r)
|
||||
return r.error();
|
||||
|
||||
auto const* field = r.value();
|
||||
if (field->getSType() != STI_ARRAY)
|
||||
return HF_ERR_NO_ARRAY;
|
||||
int32_t const sz = static_cast<STArray const*>(field)->size();
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::updateData(Bytes const& data)
|
||||
{
|
||||
auto sle = ctx.view().peek(leKey);
|
||||
if (!sle)
|
||||
return HF_ERR_LEDGER_OBJ_NOT_FOUND;
|
||||
sle->setFieldVL(sfData, data);
|
||||
ctx.view().update(sle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Hash
|
||||
WasmHostFunctionsImpl::computeSha512HalfHash(Bytes const& data)
|
||||
{
|
||||
auto const hash = sha512Half(data);
|
||||
return hash;
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::accountKeylet(AccountID const& account)
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HF_ERR_INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::account(account);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::credentialKeylet(
|
||||
AccountID const& subject,
|
||||
AccountID const& issuer,
|
||||
Bytes const& credentialType)
|
||||
{
|
||||
if (!subject || !issuer)
|
||||
return Unexpected(HF_ERR_INVALID_ACCOUNT);
|
||||
|
||||
if (credentialType.empty() ||
|
||||
credentialType.size() > maxCredentialTypeLength)
|
||||
return Unexpected(HF_ERR_INVALID_PARAMS);
|
||||
|
||||
auto const keylet =
|
||||
keylet::credential(subject, issuer, makeSlice(credentialType));
|
||||
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq)
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HF_ERR_INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::escrow(account, seq);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::oracleKeylet(
|
||||
AccountID const& account,
|
||||
std::uint32_t documentId)
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HF_ERR_INVALID_ACCOUNT);
|
||||
auto const keylet = keylet::oracle(account, documentId);
|
||||
return Bytes{keylet.key.begin(), keylet.key.end()};
|
||||
}
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId)
|
||||
{
|
||||
if (!account)
|
||||
return Unexpected(HF_ERR_INVALID_ACCOUNT);
|
||||
|
||||
if (!nftId)
|
||||
return Unexpected(HF_ERR_INVALID_PARAMS);
|
||||
|
||||
auto obj = nft::findToken(ctx.view(), account, nftId);
|
||||
if (!obj)
|
||||
return Unexpected(HF_ERR_LEDGER_OBJ_NOT_FOUND);
|
||||
|
||||
auto ouri = obj->at(~sfURI);
|
||||
if (!ouri)
|
||||
return Unexpected(HF_ERR_FIELD_NOT_FOUND);
|
||||
|
||||
Slice const s = ouri->value();
|
||||
return Bytes(s.begin(), s.end());
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::trace(
|
||||
std::string const& msg,
|
||||
Bytes const& data,
|
||||
bool asHex)
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto j = ctx.journal.error();
|
||||
#else
|
||||
auto j = ctx.journal.trace();
|
||||
#endif
|
||||
if (!asHex)
|
||||
j << "WAMR TRACE (" << leKey.key << "): " << msg << " - "
|
||||
<< 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 << "WAMR DEV TRACE (" << leKey.key << "): " << msg << " - " << hex;
|
||||
}
|
||||
|
||||
return msg.size() + data.size() * (asHex ? 2 : 1);
|
||||
}
|
||||
|
||||
int32_t
|
||||
WasmHostFunctionsImpl::traceNum(std::string const& msg, int64_t data)
|
||||
{
|
||||
#ifdef DEBUG_OUTPUT
|
||||
auto j = ctx.journal.error();
|
||||
#else
|
||||
auto j = ctx.journal.trace();
|
||||
#endif
|
||||
|
||||
j << "WAMR DEV TRACE (" << leKey.key << "): " << msg << " - " << data;
|
||||
|
||||
return msg.size() + sizeof(data);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,139 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 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 <xrpld/app/misc/WasmHostFunc.h>
|
||||
#include <xrpld/app/tx/detail/ApplyContext.h>
|
||||
|
||||
namespace ripple {
|
||||
class WasmHostFunctionsImpl : public HostFunctions
|
||||
{
|
||||
ApplyContext& ctx;
|
||||
Keylet leKey;
|
||||
|
||||
static int constexpr MAX_CACHE = 256;
|
||||
std::array<std::shared_ptr<SLE const>, MAX_CACHE> cache;
|
||||
|
||||
void const* rt_ = nullptr;
|
||||
|
||||
public:
|
||||
WasmHostFunctionsImpl(ApplyContext& ctx, Keylet const& leKey)
|
||||
: ctx(ctx), leKey(leKey)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void
|
||||
setRT(void const* rt) override
|
||||
{
|
||||
rt_ = rt;
|
||||
}
|
||||
|
||||
virtual void const*
|
||||
getRT() const override
|
||||
{
|
||||
return rt_;
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
getJournal() override
|
||||
{
|
||||
return ctx.journal;
|
||||
}
|
||||
|
||||
int32_t
|
||||
getLedgerSqn() override;
|
||||
|
||||
int32_t
|
||||
getParentLedgerTime() override;
|
||||
|
||||
Hash
|
||||
getParentLedgerHash() override;
|
||||
|
||||
int32_t
|
||||
cacheLedgerObj(Keylet const& keylet, int32_t cacheIdx) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getTxField(SField const& fname) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getCurrentLedgerObjField(SField const& fname) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getLedgerObjField(int32_t cacheIdx, SField const& fname) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getTxNestedField(Slice const& locator) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getCurrentLedgerObjNestedField(Slice const& locator) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override;
|
||||
|
||||
int32_t
|
||||
getTxArrayLen(SField const& fname) override;
|
||||
|
||||
int32_t
|
||||
getCurrentLedgerObjArrayLen(SField const& fname) override;
|
||||
|
||||
int32_t
|
||||
getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) override;
|
||||
|
||||
int32_t
|
||||
getTxNestedArrayLen(Slice const& locator) override;
|
||||
|
||||
int32_t
|
||||
getCurrentLedgerObjNestedArrayLen(Slice const& locator) override;
|
||||
|
||||
int32_t
|
||||
getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override;
|
||||
|
||||
int32_t
|
||||
updateData(Bytes const& data) override;
|
||||
|
||||
Hash
|
||||
computeSha512HalfHash(Bytes const& data) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
accountKeylet(AccountID const& account) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
credentialKeylet(
|
||||
AccountID const& subject,
|
||||
AccountID const& issuer,
|
||||
Bytes const& credentialType) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
escrowKeylet(AccountID const& account, std::uint32_t seq) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
oracleKeylet(AccountID const& account, std::uint32_t documentId) override;
|
||||
|
||||
Expected<Bytes, int32_t>
|
||||
getNFT(AccountID const& account, uint256 const& nftId) override;
|
||||
|
||||
int32_t
|
||||
trace(std::string const& msg, Bytes const& data, bool asHex) override;
|
||||
|
||||
int32_t
|
||||
traceNum(std::string const& msg, int64_t data) override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,764 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpld/app/misc/WamrVM.h>
|
||||
#include <xrpld/app/misc/WasmHostFunc.h>
|
||||
#include <xrpld/app/misc/WasmHostFuncWrapper.h>
|
||||
#include <xrpld/app/tx/detail/NFTokenUtils.h>
|
||||
|
||||
#include <xrpl/protocol/digest.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
static int32_t
|
||||
setData(
|
||||
InstanceWrapper const* rt,
|
||||
int32_t dst,
|
||||
int32_t dsz,
|
||||
uint8_t const* src,
|
||||
int32_t ssz)
|
||||
{
|
||||
if (!ssz)
|
||||
return 0;
|
||||
|
||||
if (dst < 0 || dsz < 0 || !src || ssz < 0)
|
||||
return HF_ERR_INVALID_PARAMS;
|
||||
|
||||
auto mem = rt ? rt->getMem() : wmem();
|
||||
|
||||
if (!mem.s)
|
||||
return HF_ERR_NO_MEM_EXPORTED;
|
||||
if (dst + dsz > mem.s)
|
||||
return HF_ERR_POINTER_OUT_OF_BOUNDS;
|
||||
if (ssz > dsz)
|
||||
return HF_ERR_BUFFER_TOO_SMALL;
|
||||
|
||||
memcpy(mem.p + dst, src, ssz);
|
||||
|
||||
return ssz;
|
||||
}
|
||||
|
||||
static Expected<Bytes, int32_t>
|
||||
getData(InstanceWrapper const* rt, int32_t src, int32_t ssz)
|
||||
{
|
||||
if (src < 0 || ssz <= 0)
|
||||
return Unexpected(HF_ERR_INVALID_PARAMS);
|
||||
|
||||
auto mem = rt ? rt->getMem() : wmem();
|
||||
if (!mem.s)
|
||||
return Unexpected(HF_ERR_NO_MEM_EXPORTED);
|
||||
|
||||
if (src + ssz > mem.s)
|
||||
return Unexpected(HF_ERR_POINTER_OUT_OF_BOUNDS);
|
||||
|
||||
Bytes data(mem.p + src, mem.p + src + ssz);
|
||||
return data;
|
||||
}
|
||||
|
||||
static Expected<AccountID, int32_t>
|
||||
getDataAccount(InstanceWrapper const* rt, int32_t ptr, int32_t sz)
|
||||
{
|
||||
auto const r = getData(rt, ptr, sz);
|
||||
if (!r || (r->size() != AccountID::bytes))
|
||||
return Unexpected(HF_ERR_INVALID_PARAMS);
|
||||
|
||||
return AccountID::fromVoid(r->data());
|
||||
}
|
||||
|
||||
static Expected<std::string, int32_t>
|
||||
getDataString(InstanceWrapper const* rt, int32_t src, int32_t ssz)
|
||||
{
|
||||
if (src < 0 || ssz <= 0)
|
||||
return Unexpected(HF_ERR_INVALID_PARAMS);
|
||||
|
||||
auto mem = rt ? rt->getMem() : wmem();
|
||||
if (!mem.s)
|
||||
return Unexpected(HF_ERR_NO_MEM_EXPORTED);
|
||||
|
||||
if (src + ssz > mem.s)
|
||||
return Unexpected(HF_ERR_POINTER_OUT_OF_BOUNDS);
|
||||
|
||||
std::string data(mem.p + src, mem.p + src + ssz);
|
||||
return data;
|
||||
}
|
||||
|
||||
#define RET(x) \
|
||||
results->data[0] = WASM_I32_VAL(x); \
|
||||
results->num_elems = 1; \
|
||||
return nullptr;
|
||||
|
||||
wasm_trap_t*
|
||||
getLedgerSqnOld_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
int32_t const sqn = hf->getLedgerSqn();
|
||||
RET(sqn);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getLedgerSqn_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
int32_t const sqn = hf->getLedgerSqn();
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[0].of.i32,
|
||||
params->data[1].of.i32,
|
||||
reinterpret_cast<uint8_t const*>(&sqn),
|
||||
static_cast<int32_t>(sizeof(sqn))));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getParentLedgerTime_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
int32_t const ltime = hf->getParentLedgerTime();
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[0].of.i32,
|
||||
params->data[1].of.i32,
|
||||
reinterpret_cast<uint8_t const*>(<ime),
|
||||
static_cast<int32_t>(sizeof(ltime))));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getParentLedgerHash_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
Hash const hash = hf->getParentLedgerHash();
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[0].of.i32,
|
||||
params->data[1].of.i32,
|
||||
hash.data(),
|
||||
static_cast<int32_t>(hash.size())));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
cacheLedgerObj_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
if (r->size() != uint256::bytes)
|
||||
{
|
||||
RET(HF_ERR_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
uint256 const key(uint256::fromVoid(r->data()));
|
||||
int32_t const idx =
|
||||
hf->cacheLedgerObj(keylet::unchecked(key), params->data[2].of.i32);
|
||||
RET(idx);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getTxField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
auto const it = m.find(params->data[0].of.i32);
|
||||
if (it == m.end())
|
||||
{
|
||||
RET(HF_ERR_INVALID_FIELD);
|
||||
}
|
||||
auto const& fname(*it->second);
|
||||
|
||||
auto fieldData = hf->getTxField(fname);
|
||||
if (!fieldData)
|
||||
{
|
||||
RET(fieldData.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[1].of.i32,
|
||||
params->data[2].of.i32,
|
||||
fieldData->data(),
|
||||
fieldData->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
auto const it = m.find(params->data[0].of.i32);
|
||||
if (it == m.end())
|
||||
{
|
||||
RET(HF_ERR_INVALID_FIELD);
|
||||
}
|
||||
auto const& fname(*it->second);
|
||||
|
||||
auto fieldData = hf->getCurrentLedgerObjField(fname);
|
||||
if (!fieldData)
|
||||
{
|
||||
RET(fieldData.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[1].of.i32,
|
||||
params->data[2].of.i32,
|
||||
fieldData->data(),
|
||||
fieldData->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getLedgerObjField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
auto const it = m.find(params->data[1].of.i32);
|
||||
if (it == m.end())
|
||||
{
|
||||
RET(HF_ERR_INVALID_FIELD);
|
||||
}
|
||||
auto const& fname(*it->second);
|
||||
|
||||
auto fieldData = hf->getLedgerObjField(params->data[0].of.i32, fname);
|
||||
if (!fieldData)
|
||||
{
|
||||
RET(fieldData.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[2].of.i32,
|
||||
params->data[3].of.i32,
|
||||
fieldData->data(),
|
||||
fieldData->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getTxNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
auto fieldData = hf->getTxNestedField(makeSlice(r.value()));
|
||||
if (!fieldData)
|
||||
{
|
||||
RET(fieldData.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[2].of.i32,
|
||||
params->data[3].of.i32,
|
||||
fieldData->data(),
|
||||
fieldData->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
auto fieldData = hf->getCurrentLedgerObjNestedField(makeSlice(r.value()));
|
||||
if (!fieldData)
|
||||
{
|
||||
RET(fieldData.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[2].of.i32,
|
||||
params->data[3].of.i32,
|
||||
fieldData->data(),
|
||||
fieldData->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getLedgerObjNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[1].of.i32, params->data[2].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
auto fieldData = hf->getLedgerObjNestedField(
|
||||
params->data[0].of.i32, makeSlice(r.value()));
|
||||
if (!fieldData)
|
||||
{
|
||||
RET(fieldData.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[3].of.i32,
|
||||
params->data[4].of.i32,
|
||||
fieldData->data(),
|
||||
fieldData->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getTxArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
auto const it = m.find(params->data[0].of.i32);
|
||||
if (it == m.end())
|
||||
{
|
||||
RET(HF_ERR_INVALID_FIELD);
|
||||
}
|
||||
auto const& fname(*it->second);
|
||||
|
||||
int32_t sz = hf->getTxArrayLen(fname);
|
||||
RET(sz);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
auto const it = m.find(params->data[0].of.i32);
|
||||
if (it == m.end())
|
||||
{
|
||||
RET(HF_ERR_INVALID_FIELD);
|
||||
}
|
||||
auto const& fname(*it->second);
|
||||
|
||||
int32_t sz = hf->getCurrentLedgerObjArrayLen(fname);
|
||||
RET(sz);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getLedgerObjArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
// auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const& m = SField::getKnownCodeToField();
|
||||
auto const it = m.find(params->data[1].of.i32);
|
||||
if (it == m.end())
|
||||
{
|
||||
RET(HF_ERR_INVALID_FIELD);
|
||||
}
|
||||
auto const& fname(*it->second);
|
||||
|
||||
int32_t sz = hf->getLedgerObjArrayLen(params->data[0].of.i32, fname);
|
||||
RET(sz);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getTxNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
int32_t sz = hf->getTxNestedArrayLen(makeSlice(r.value()));
|
||||
RET(sz);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
int32_t sz = hf->getCurrentLedgerObjNestedArrayLen(makeSlice(r.value()));
|
||||
RET(sz);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getLedgerObjNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[1].of.i32, params->data[2].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
int32_t sz = hf->getLedgerObjNestedArrayLen(
|
||||
params->data[0].of.i32, makeSlice(r.value()));
|
||||
RET(sz);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
updateData_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
if (params->data[1].of.i32 > maxWasmDataLength)
|
||||
{
|
||||
RET(HF_ERR_DATA_FIELD_TOO_LARGE)
|
||||
}
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
RET(hf->updateData(r.value()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
computeSha512HalfHash_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const r = getData(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!r)
|
||||
{
|
||||
RET(r.error());
|
||||
}
|
||||
|
||||
auto const hash = hf->computeSha512HalfHash(r.value());
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[2].of.i32,
|
||||
params->data[3].of.i32,
|
||||
hash.data(),
|
||||
hash.size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
accountKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const acc =
|
||||
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!acc)
|
||||
{
|
||||
RET(acc.error());
|
||||
}
|
||||
|
||||
auto const k = hf->accountKeylet(acc.value());
|
||||
if (!k)
|
||||
{
|
||||
RET(k.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[2].of.i32,
|
||||
params->data[3].of.i32,
|
||||
k->data(),
|
||||
k->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
credentialKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const subject =
|
||||
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!subject)
|
||||
{
|
||||
RET(subject.error());
|
||||
}
|
||||
|
||||
auto const issuer =
|
||||
getDataAccount(rt, params->data[2].of.i32, params->data[3].of.i32);
|
||||
if (!issuer)
|
||||
{
|
||||
RET(issuer.error());
|
||||
}
|
||||
|
||||
auto const credType =
|
||||
getData(rt, params->data[4].of.i32, params->data[5].of.i32);
|
||||
if (!credType)
|
||||
{
|
||||
RET(credType.error());
|
||||
}
|
||||
|
||||
auto const k =
|
||||
hf->credentialKeylet(subject.value(), issuer.value(), credType.value());
|
||||
if (!k)
|
||||
{
|
||||
RET(k.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[6].of.i32,
|
||||
params->data[7].of.i32,
|
||||
k->data(),
|
||||
k->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
escrowKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const acc =
|
||||
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!acc)
|
||||
{
|
||||
RET(acc.error());
|
||||
}
|
||||
|
||||
auto const k = hf->escrowKeylet(acc.value(), params->data[2].of.i32);
|
||||
if (!k)
|
||||
{
|
||||
RET(k.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[3].of.i32,
|
||||
params->data[4].of.i32,
|
||||
k->data(),
|
||||
k->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
oracleKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const acc =
|
||||
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!acc)
|
||||
{
|
||||
RET(acc.error());
|
||||
}
|
||||
|
||||
auto const k = hf->oracleKeylet(acc.value(), params->data[2].of.i32);
|
||||
if (!k)
|
||||
{
|
||||
RET(k.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[3].of.i32,
|
||||
params->data[4].of.i32,
|
||||
k->data(),
|
||||
k->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const acc =
|
||||
getDataAccount(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!acc)
|
||||
{
|
||||
RET(acc.error());
|
||||
}
|
||||
|
||||
auto const nftRaw =
|
||||
getData(rt, params->data[2].of.i32, params->data[3].of.i32);
|
||||
if (!nftRaw)
|
||||
{
|
||||
RET(nftRaw.error());
|
||||
}
|
||||
|
||||
if (nftRaw->size() != uint256::bytes)
|
||||
{
|
||||
hf->getJournal().trace()
|
||||
<< "WAMR getNFT: Invalid NFT data size: " << nftRaw->size()
|
||||
<< ", expected " << (uint256::bytes);
|
||||
RET(HF_ERR_INVALID_PARAMS);
|
||||
}
|
||||
|
||||
uint256 const ntfId(uint256::fromVoid(nftRaw->data()));
|
||||
auto const nftURI = hf->getNFT(acc.value(), ntfId);
|
||||
if (!nftURI)
|
||||
{
|
||||
RET(nftURI.error());
|
||||
}
|
||||
|
||||
RET(setData(
|
||||
rt,
|
||||
params->data[4].of.i32,
|
||||
params->data[5].of.i32,
|
||||
nftURI->data(),
|
||||
nftURI->size()));
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const msg =
|
||||
getDataString(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!msg)
|
||||
{
|
||||
RET(msg.error());
|
||||
}
|
||||
|
||||
auto const data =
|
||||
getData(rt, params->data[2].of.i32, params->data[3].of.i32);
|
||||
if (!data)
|
||||
{
|
||||
RET(data.error());
|
||||
}
|
||||
|
||||
auto const e = hf->trace(msg.value(), data.value(), params->data[4].of.i32);
|
||||
RET(e);
|
||||
}
|
||||
|
||||
wasm_trap_t*
|
||||
traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results)
|
||||
{
|
||||
auto* hf = reinterpret_cast<HostFunctions*>(env);
|
||||
auto const* rt = reinterpret_cast<InstanceWrapper const*>(hf->getRT());
|
||||
|
||||
auto const msg =
|
||||
getDataString(rt, params->data[0].of.i32, params->data[1].of.i32);
|
||||
if (!msg)
|
||||
{
|
||||
RET(msg.error());
|
||||
}
|
||||
|
||||
auto const e = hf->traceNum(msg.value(), params->data[2].of.i64);
|
||||
RET(e);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,222 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpld/app/misc/WamrVM.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
using getLedgerSqnOld_proto = int32_t();
|
||||
wasm_trap_t*
|
||||
getLedgerSqnOld_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerSqn_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerSqn_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getParentLedgerTime_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getParentLedgerHash_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
cacheLedgerObj_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getTxField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getTxNestedField_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getTxNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjNestedField_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjNestedField_proto =
|
||||
int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjNestedField_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getTxArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t*
|
||||
getTxArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getTxNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getCurrentLedgerObjNestedArrayLen_proto =
|
||||
int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getCurrentLedgerObjNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getLedgerObjNestedArrayLen_proto =
|
||||
int32_t(int32_t, uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
getLedgerObjNestedArrayLen_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using updateData_proto = int32_t(uint8_t const*, int32_t);
|
||||
wasm_trap_t*
|
||||
updateData_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using computeSha512HalfHash_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
computeSha512HalfHash_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
accountKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using credentialKeylet_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t*
|
||||
credentialKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using escrowKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
escrowKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using oracleKeylet_proto =
|
||||
int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t);
|
||||
wasm_trap_t*
|
||||
oracleKeylet_wrap(
|
||||
void* env,
|
||||
wasm_val_vec_t const* params,
|
||||
wasm_val_vec_t* results);
|
||||
|
||||
using getNFT_proto = int32_t(
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t const*,
|
||||
int32_t,
|
||||
uint8_t*,
|
||||
int32_t);
|
||||
wasm_trap_t*
|
||||
getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using trace_proto =
|
||||
int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t);
|
||||
wasm_trap_t*
|
||||
trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t);
|
||||
wasm_trap_t*
|
||||
traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,272 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpl/basics/base_uint.h>
|
||||
|
||||
#include <boost/function_types/function_arity.hpp>
|
||||
#include <boost/function_types/parameter_types.hpp>
|
||||
#include <boost/function_types/result_type.hpp>
|
||||
#include <boost/mpl/vector.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace bft = boost::function_types;
|
||||
|
||||
namespace ripple {
|
||||
|
||||
using Bytes = std::vector<std::uint8_t>;
|
||||
using Hash = ripple::uint256;
|
||||
|
||||
struct wmem
|
||||
{
|
||||
std::uint8_t* p = nullptr;
|
||||
std::size_t s = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct WasmResult
|
||||
{
|
||||
T result;
|
||||
int64_t cost;
|
||||
};
|
||||
typedef WasmResult<bool> EscrowResult;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum WasmTypes { WT_I32, WT_I64, WT_F32, WT_F64, WT_U8V };
|
||||
|
||||
struct WasmImportFunc
|
||||
{
|
||||
std::string name;
|
||||
std::optional<WasmTypes> result;
|
||||
std::vector<WasmTypes> params;
|
||||
void* udata = nullptr;
|
||||
// wasm_func_callback_with_env_t
|
||||
void* wrap = nullptr;
|
||||
uint32_t gas = 0;
|
||||
};
|
||||
|
||||
#define WASM_IMPORT_FUNC(v, f, ...) \
|
||||
WasmImpFunc<f##_proto>( \
|
||||
v, #f, reinterpret_cast<void*>(&f##_wrap), ##__VA_ARGS__)
|
||||
|
||||
#define WASM_IMPORT_FUNC2(v, f, n, ...) \
|
||||
WasmImpFunc<f##_proto>( \
|
||||
v, n, reinterpret_cast<void*>(&f##_wrap), ##__VA_ARGS__)
|
||||
|
||||
template <int N, int C, typename mpl>
|
||||
void
|
||||
WasmImpArgs(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (N < C)
|
||||
{
|
||||
using at = typename boost::mpl::at_c<mpl, N>::type;
|
||||
if constexpr (std::is_pointer_v<at>)
|
||||
e.params.push_back(WT_I32);
|
||||
else if constexpr (std::is_same_v<at, std::int32_t>)
|
||||
e.params.push_back(WT_I32);
|
||||
else if constexpr (std::is_same_v<at, std::int64_t>)
|
||||
e.params.push_back(WT_I64);
|
||||
else if constexpr (std::is_same_v<at, float>)
|
||||
e.params.push_back(WT_F32);
|
||||
else if constexpr (std::is_same_v<at, double>)
|
||||
e.params.push_back(WT_F64);
|
||||
else
|
||||
static_assert(std::is_pointer_v<at>, "Unsupported argument type");
|
||||
|
||||
return WasmImpArgs<N + 1, C, mpl>(e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
template <typename rt>
|
||||
void
|
||||
WasmImpRet(WasmImportFunc& e)
|
||||
{
|
||||
if constexpr (std::is_pointer_v<rt>)
|
||||
e.result = WT_I32;
|
||||
else if constexpr (std::is_same_v<rt, std::int32_t>)
|
||||
e.result = WT_I32;
|
||||
else if constexpr (std::is_same_v<rt, std::int64_t>)
|
||||
e.result = WT_I64;
|
||||
else if constexpr (std::is_same_v<rt, float>)
|
||||
e.result = WT_F32;
|
||||
else if constexpr (std::is_same_v<rt, double>)
|
||||
e.result = WT_F64;
|
||||
else if constexpr (std::is_void_v<rt>)
|
||||
e.result.reset();
|
||||
#if (defined(__GNUC__) && (__GNUC__ >= 14)) || \
|
||||
((defined(__clang_major__)) && (__clang_major__ >= 18))
|
||||
else
|
||||
static_assert(false, "Unsupported return type");
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFuncHelper(WasmImportFunc& e)
|
||||
{
|
||||
using rt = typename bft::result_type<F>::type;
|
||||
using pt = typename bft::parameter_types<F>::type;
|
||||
// typename boost::mpl::at_c<mpl, N>::type
|
||||
|
||||
WasmImpRet<rt>(e);
|
||||
WasmImpArgs<0, bft::function_arity<F>::value, pt>(e);
|
||||
// WasmImpWrap(e, std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
void
|
||||
WasmImpFunc(
|
||||
std::vector<WasmImportFunc>& v,
|
||||
std::string_view imp_name,
|
||||
void* f_wrap,
|
||||
void* data = nullptr,
|
||||
uint32_t gas = 0)
|
||||
{
|
||||
WasmImportFunc e;
|
||||
e.name = imp_name;
|
||||
e.udata = data;
|
||||
e.wrap = f_wrap;
|
||||
e.gas = gas;
|
||||
WasmImpFuncHelper<F>(e);
|
||||
v.push_back(std::move(e));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct WasmParamVec
|
||||
{
|
||||
std::uint8_t const* d = nullptr;
|
||||
std::int32_t sz = 0;
|
||||
};
|
||||
|
||||
struct WasmParam
|
||||
{
|
||||
WasmTypes type = WT_I32;
|
||||
union
|
||||
{
|
||||
std::int32_t i32;
|
||||
std::int64_t i64 = 0;
|
||||
float f32;
|
||||
double f64;
|
||||
WasmParamVec u8v;
|
||||
} of;
|
||||
};
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int32_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WT_I32, .of = {.i32 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::int64_t p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WT_I64, .of = {.i64 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, float p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WT_F32, .of = {.f32 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, double p, Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WT_F64, .of = {.f64 = p}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(
|
||||
std::vector<WasmParam>& v,
|
||||
std::uint8_t const* dt,
|
||||
std::int32_t sz,
|
||||
Types&&... args)
|
||||
{
|
||||
v.push_back({.type = WT_U8V, .of = {.u8v = {.d = dt, .sz = sz}}});
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, Bytes const& p, Types&&... args)
|
||||
{
|
||||
wasmParamsHlp(
|
||||
v,
|
||||
p.data(),
|
||||
static_cast<std::int32_t>(p.size()),
|
||||
std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(
|
||||
std::vector<WasmParam>& v,
|
||||
std::string_view const& p,
|
||||
Types&&... args)
|
||||
{
|
||||
wasmParamsHlp(
|
||||
v,
|
||||
reinterpret_cast<std::uint8_t const*>(p.data()),
|
||||
static_cast<std::int32_t>(p.size()),
|
||||
std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v, std::string const& p, Types&&... args)
|
||||
{
|
||||
wasmParamsHlp(
|
||||
v,
|
||||
reinterpret_cast<std::uint8_t const*>(p.c_str()),
|
||||
static_cast<std::int32_t>(p.size()),
|
||||
std::forward<Types>(args)...);
|
||||
}
|
||||
|
||||
inline void
|
||||
wasmParamsHlp(std::vector<WasmParam>& v)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
template <class... Types>
|
||||
inline std::vector<WasmParam>
|
||||
wasmParams(Types&&... args)
|
||||
{
|
||||
std::vector<WasmParam> v;
|
||||
v.reserve(sizeof...(args));
|
||||
wasmParamsHlp(v, std::forward<Types>(args)...);
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,216 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpld/app/misc/WamrVM.h>
|
||||
#include <xrpld/app/misc/WasmHostFunc.h>
|
||||
#include <xrpld/app/misc/WasmHostFuncWrapper.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
static std::vector<WasmImportFunc>
|
||||
createImports(HostFunctions* hfs)
|
||||
{
|
||||
std::vector<WasmImportFunc> imports;
|
||||
|
||||
if (hfs)
|
||||
{
|
||||
// TODO: remove after escrow_test wasm module will be updated
|
||||
WASM_IMPORT_FUNC2(imports, getLedgerSqnOld, "getLedgerSqn", hfs);
|
||||
|
||||
WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, getParentLedgerTime, "get_parent_ledger_time", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, getParentLedgerHash, "get_parent_ledger_hash", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, cacheLedgerObj, "cache_ledger_obj", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, getTxField, "get_tx_field", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports,
|
||||
getCurrentLedgerObjField,
|
||||
"get_current_ledger_obj_field",
|
||||
hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, getLedgerObjField, "get_ledger_obj_field", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, getTxNestedField, "get_tx_nested_field", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports,
|
||||
getCurrentLedgerObjNestedField,
|
||||
"get_current_ledger_obj_nested_field",
|
||||
hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports,
|
||||
getLedgerObjNestedField,
|
||||
"get_ledger_obj_nested_field",
|
||||
hfs);
|
||||
WASM_IMPORT_FUNC2(imports, getTxArrayLen, "get_tx_array_len", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports,
|
||||
getCurrentLedgerObjArrayLen,
|
||||
"get_current_ledger_obj_array_len",
|
||||
hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, getLedgerObjArrayLen, "get_ledger_obj_array_len", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, getTxNestedArrayLen, "get_tx_nested_array_len", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports,
|
||||
getCurrentLedgerObjNestedArrayLen,
|
||||
"get_current_ledger_obj_nested_array_len",
|
||||
hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports,
|
||||
getLedgerObjNestedArrayLen,
|
||||
"get_ledger_obj_nested_array_len",
|
||||
hfs);
|
||||
WASM_IMPORT_FUNC2(imports, updateData, "update_data", hfs);
|
||||
WASM_IMPORT_FUNC2(
|
||||
imports, computeSha512HalfHash, "compute_sha512_half", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, accountKeylet, "account_keylet", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, credentialKeylet, "credential_keylet", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, escrowKeylet, "escrow_keylet", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, oracleKeylet, "oracle_keylet", hfs);
|
||||
WASM_IMPORT_FUNC2(imports, getNFT, "get_NFT", hfs);
|
||||
WASM_IMPORT_FUNC(imports, trace, hfs);
|
||||
WASM_IMPORT_FUNC2(imports, traceNum, "trace_num", hfs);
|
||||
}
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
Expected<EscrowResult, TER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
HostFunctions* hfs,
|
||||
int64_t gasLimit,
|
||||
beast::Journal j)
|
||||
{
|
||||
// create VM and set cost limit
|
||||
auto& vm = WasmEngine::instance();
|
||||
vm.initMaxPages(MAX_PAGES);
|
||||
|
||||
auto const ret = vm.run(
|
||||
wasmCode,
|
||||
funcName,
|
||||
params,
|
||||
createImports(hfs),
|
||||
hfs,
|
||||
gasLimit,
|
||||
hfs ? hfs->getJournal() : j);
|
||||
|
||||
// std::cout << "runEscrowWasm, mod size: " << wasmCode.size()
|
||||
// << ", gasLimit: " << gasLimit << ", funcName: " << funcName;
|
||||
|
||||
if (!ret.has_value())
|
||||
{
|
||||
// std::cout << ", error: " << ret.error() << std::endl;
|
||||
return Unexpected<TER>(ret.error());
|
||||
}
|
||||
|
||||
// std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost
|
||||
// << std::endl;
|
||||
return EscrowResult{ret->result > 0, ret->cost};
|
||||
}
|
||||
|
||||
NotTEC
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
HostFunctions* hfs,
|
||||
beast::Journal j)
|
||||
{
|
||||
// create VM and set cost limit
|
||||
auto& vm = WasmEngine::instance();
|
||||
vm.initMaxPages(MAX_PAGES);
|
||||
|
||||
auto const ret = vm.check(
|
||||
wasmCode,
|
||||
funcName,
|
||||
params,
|
||||
createImports(hfs),
|
||||
hfs ? hfs->getJournal() : j);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WasmEngine::WasmEngine() : impl(std::make_unique<WamrEngine>())
|
||||
{
|
||||
}
|
||||
|
||||
WasmEngine&
|
||||
WasmEngine::instance()
|
||||
{
|
||||
static WasmEngine e;
|
||||
return e;
|
||||
}
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
WasmEngine::run(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
std::vector<WasmImportFunc> const& imports,
|
||||
HostFunctions* hfs,
|
||||
int64_t gasLimit,
|
||||
beast::Journal j)
|
||||
{
|
||||
return impl->run(wasmCode, funcName, params, imports, hfs, gasLimit, j);
|
||||
}
|
||||
|
||||
NotTEC
|
||||
WasmEngine::check(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params,
|
||||
std::vector<WasmImportFunc> const& imports,
|
||||
beast::Journal j)
|
||||
{
|
||||
return impl->check(wasmCode, funcName, params, imports, j);
|
||||
}
|
||||
|
||||
std::int32_t
|
||||
WasmEngine::initMaxPages(std::int32_t def)
|
||||
{
|
||||
return impl->initMaxPages(def);
|
||||
}
|
||||
|
||||
void*
|
||||
WasmEngine::newTrap(std::string_view msg)
|
||||
{
|
||||
return impl->newTrap(msg);
|
||||
}
|
||||
|
||||
beast::Journal
|
||||
WasmEngine::getJournal() const
|
||||
{
|
||||
return impl->getJournal();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -0,0 +1,106 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpld/app/misc/WasmHostFunc.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
static std::string_view const W_ENV = "env";
|
||||
static std::string_view const W_HOST_LIB = "host_lib";
|
||||
static std::string_view const W_MEM = "memory";
|
||||
static std::string_view const W_STORE = "store";
|
||||
static std::string_view const W_LOAD = "load";
|
||||
static std::string_view const W_SIZE = "size";
|
||||
static std::string_view const W_ALLOC = "allocate";
|
||||
static std::string_view const W_DEALLOC = "deallocate";
|
||||
static std::string_view const W_PROC_EXIT = "proc_exit";
|
||||
|
||||
uint32_t const MAX_PAGES = 128; // 8MB = 64KB*128
|
||||
|
||||
class WamrEngine;
|
||||
class WasmEngine
|
||||
{
|
||||
std::unique_ptr<WamrEngine> const impl;
|
||||
|
||||
WasmEngine();
|
||||
|
||||
WasmEngine(WasmEngine const&) = delete;
|
||||
WasmEngine(WasmEngine&&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine const&) = delete;
|
||||
WasmEngine&
|
||||
operator=(WasmEngine&&) = delete;
|
||||
|
||||
public:
|
||||
~WasmEngine() = default;
|
||||
|
||||
static WasmEngine&
|
||||
instance();
|
||||
|
||||
Expected<WasmResult<int32_t>, TER>
|
||||
run(Bytes const& wasmCode,
|
||||
std::string_view funcName = {},
|
||||
std::vector<WasmParam> const& params = {},
|
||||
std::vector<WasmImportFunc> const& imports = {},
|
||||
HostFunctions* hfs = nullptr,
|
||||
int64_t gasLimit = -1,
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
NotTEC
|
||||
check(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
std::vector<WasmImportFunc> const& imports = {},
|
||||
beast::Journal j = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
std::int32_t
|
||||
initMaxPages(std::int32_t def);
|
||||
|
||||
// Host functions helper functionality
|
||||
void*
|
||||
newTrap(std::string_view msg = {});
|
||||
|
||||
beast::Journal
|
||||
getJournal() const;
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Expected<EscrowResult, TER>
|
||||
runEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
HostFunctions* hfs = nullptr,
|
||||
int64_t gasLimit = -1,
|
||||
beast::Journal j = beast::Journal(beast::Journal::getNullSink()));
|
||||
|
||||
NotTEC
|
||||
preflightEscrowWasm(
|
||||
Bytes const& wasmCode,
|
||||
std::string_view funcName,
|
||||
std::vector<WasmParam> const& params = {},
|
||||
HostFunctions* hfs = nullptr,
|
||||
beast::Journal j = beast::Journal(beast::Journal::getNullSink()));
|
||||
|
||||
} // namespace ripple
|
||||
@@ -150,8 +150,8 @@ ammLPHolds(
|
||||
}
|
||||
amount.setIssuer(ammAccount);
|
||||
|
||||
JLOG(j.trace()) << "ammLPHolds:"
|
||||
<< " lpAccount=" << to_string(lpAccount)
|
||||
JLOG(j.trace()) << "ammLPHolds:" << " lpAccount="
|
||||
<< to_string(lpAccount)
|
||||
<< " amount=" << amount.getFullText();
|
||||
}
|
||||
|
||||
|
||||
@@ -1504,11 +1504,11 @@ TxQ::accept(Application& app, OpenView& view)
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "Queued transaction " << candidateIter->txID
|
||||
<< " failed with " << transToken(txnResult)
|
||||
<< ". Leave in queue."
|
||||
<< " Applied: " << didApply
|
||||
<< ". Flags: " << candidateIter->flags;
|
||||
JLOG(j_.debug())
|
||||
<< "Queued transaction " << candidateIter->txID
|
||||
<< " failed with " << transToken(txnResult)
|
||||
<< ". Leave in queue." << " Applied: " << didApply
|
||||
<< ". Flags: " << candidateIter->flags;
|
||||
if (account.retryPenalty && candidateIter->retriesRemaining > 2)
|
||||
candidateIter->retriesRemaining = 1;
|
||||
else
|
||||
|
||||
@@ -238,8 +238,7 @@ Pathfinder::findPaths(
|
||||
mSource = STPathElement(account, mSrcCurrency, issuer);
|
||||
auto issuerString =
|
||||
mSrcIssuer ? to_string(*mSrcIssuer) : std::string("none");
|
||||
JLOG(j_.trace()) << "findPaths>"
|
||||
<< " mSrcAccount=" << mSrcAccount
|
||||
JLOG(j_.trace()) << "findPaths>" << " mSrcAccount=" << mSrcAccount
|
||||
<< " mDstAccount=" << mDstAccount
|
||||
<< " mDstAmount=" << mDstAmount.getFullText()
|
||||
<< " mSrcCurrency=" << mSrcCurrency
|
||||
|
||||
@@ -190,8 +190,7 @@ protected:
|
||||
logStringImpl(char const* name) const
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << name << ": "
|
||||
<< "\ninIss: " << book_.in.account
|
||||
ostr << name << ": " << "\ninIss: " << book_.in.account
|
||||
<< "\noutIss: " << book_.out.account
|
||||
<< "\ninCur: " << book_.in.currency
|
||||
<< "\noutCur: " << book_.out.currency;
|
||||
|
||||
@@ -205,8 +205,7 @@ protected:
|
||||
logStringImpl(char const* name) const
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << name << ": "
|
||||
<< "\nSrc: " << src_ << "\nDst: " << dst_;
|
||||
ostr << name << ": " << "\nSrc: " << src_ << "\nDst: " << dst_;
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -132,8 +132,7 @@ protected:
|
||||
logStringImpl(char const* name) const
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << name << ": "
|
||||
<< "\nAcc: " << acc_;
|
||||
ostr << name << ": " << "\nAcc: " << acc_;
|
||||
return ostr.str();
|
||||
}
|
||||
|
||||
|
||||
@@ -238,8 +238,8 @@ rangeCheckedCast(C c)
|
||||
/* This should never happen */
|
||||
UNREACHABLE("ripple::rangeCheckedCast : domain error");
|
||||
JLOG(debugLog().error())
|
||||
<< "rangeCheckedCast domain error:"
|
||||
<< " value = " << c << " min = " << std::numeric_limits<T>::lowest()
|
||||
<< "rangeCheckedCast domain error:" << " value = " << c
|
||||
<< " min = " << std::numeric_limits<T>::lowest()
|
||||
<< " max: " << std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
|
||||
@@ -106,6 +106,13 @@ public:
|
||||
view_->deliver(amount);
|
||||
}
|
||||
|
||||
/** Sets the gas used in the metadata */
|
||||
void
|
||||
setGasUsed(std::uint32_t const& gasUsed)
|
||||
{
|
||||
view_->setGasUsed(gasUsed);
|
||||
}
|
||||
|
||||
/** Discard changes and start fresh. */
|
||||
void
|
||||
discard();
|
||||
|
||||
@@ -212,8 +212,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
|
||||
if (flags & tfBatchMask)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
|
||||
<< "invalid flags.";
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]:" << "invalid flags.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
@@ -221,23 +221,24 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
flags &
|
||||
(tfAllOrNothing | tfOnlyOne | tfUntilFailure | tfIndependent)) != 1)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
|
||||
<< "too many flags.";
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]:" << "too many flags.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
auto const& rawTxns = ctx.tx.getFieldArray(sfRawTransactions);
|
||||
if (rawTxns.size() <= 1)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
|
||||
<< "txns array must have at least 2 entries.";
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]:" << "txns array must have at least 2 entries.";
|
||||
return temARRAY_EMPTY;
|
||||
}
|
||||
|
||||
if (rawTxns.size() > maxBatchTxCount)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]:"
|
||||
<< "txns array exceeds 8 entries.";
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
|
||||
<< "]:" << "txns array exceeds 8 entries.";
|
||||
return temARRAY_TOO_LARGE;
|
||||
}
|
||||
|
||||
@@ -252,50 +253,53 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
auto const hash = stx.getTransactionID();
|
||||
if (!uniqueHashes.emplace(hash).second)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "duplicate Txn found. "
|
||||
<< "txID: " << hash;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "duplicate Txn found. " << "txID: " << hash;
|
||||
return temREDUNDANT;
|
||||
}
|
||||
|
||||
if (stx.getFieldU16(sfTransactionType) == ttBATCH)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "batch cannot have an inner batch txn. "
|
||||
<< "txID: " << hash;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "batch cannot have an inner batch txn. "
|
||||
<< "txID: " << hash;
|
||||
return temINVALID;
|
||||
}
|
||||
|
||||
if (!(stx.getFlags() & tfInnerBatchTxn))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn must have the tfInnerBatchTxn flag. "
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "inner txn must have the tfInnerBatchTxn flag. "
|
||||
<< "txID: " << hash;
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
if (stx.isFieldPresent(sfTxnSignature))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn cannot include TxnSignature. "
|
||||
<< "txID: " << hash;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "inner txn cannot include TxnSignature. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_SIGNATURE;
|
||||
}
|
||||
|
||||
if (stx.isFieldPresent(sfSigners))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn cannot include Signers. "
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "inner txn cannot include Signers. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
if (!stx.getSigningPubKey().empty())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn SigningPubKey must be empty. "
|
||||
<< "txID: " << hash;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "inner txn SigningPubKey must be empty. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_REGKEY;
|
||||
}
|
||||
|
||||
@@ -304,10 +308,10 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
ctx.app, ctx.rules, parentBatchId, stx, tapBATCH, ctx.j);
|
||||
preflightResult.ter != tesSUCCESS)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn preflight failed: "
|
||||
<< transHuman(preflightResult.ter) << " "
|
||||
<< "txID: " << hash;
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "inner txn preflight failed: "
|
||||
<< transHuman(preflightResult.ter) << " " << "txID: " << hash;
|
||||
return temINVALID_INNER_BATCH;
|
||||
}
|
||||
|
||||
@@ -315,8 +319,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
if (auto const fee = stx.getFieldAmount(sfFee);
|
||||
!fee.native() || fee.xrp() != beast::zero)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "inner txn must have a fee of 0. "
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "inner txn must have a fee of 0. "
|
||||
<< "txID: " << hash;
|
||||
return temBAD_FEE;
|
||||
}
|
||||
@@ -353,8 +357,7 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "duplicate sequence found: "
|
||||
<< "txID: " << hash;
|
||||
<< "duplicate sequence found: " << "txID: " << hash;
|
||||
return temREDUNDANT;
|
||||
}
|
||||
}
|
||||
@@ -366,8 +369,7 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "duplicate ticket found: "
|
||||
<< "txID: " << hash;
|
||||
<< "duplicate ticket found: " << "txID: " << hash;
|
||||
return temREDUNDANT;
|
||||
}
|
||||
}
|
||||
@@ -393,8 +395,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
// Check that the batch signers array is not too large.
|
||||
if (signers.size() > maxBatchTxCount)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "signers array exceeds 8 entries.";
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "signers array exceeds 8 entries.";
|
||||
return temARRAY_TOO_LARGE;
|
||||
}
|
||||
|
||||
@@ -417,8 +419,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
if (!batchSigners.insert(signerAccount).second)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "duplicate signer found: " << signerAccount;
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "duplicate signer found: " << signerAccount;
|
||||
return temREDUNDANT;
|
||||
}
|
||||
|
||||
@@ -426,8 +428,9 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
// Remove it if it does, as it can be crossed off the list.
|
||||
if (requiredSigners.erase(signerAccount) == 0)
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "no account signature for inner txn.";
|
||||
JLOG(ctx.j.debug())
|
||||
<< "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "no account signature for inner txn.";
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
}
|
||||
@@ -447,8 +450,8 @@ Batch::preflight(PreflightContext const& ctx)
|
||||
|
||||
if (!requiredSigners.empty())
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
|
||||
<< "invalid batch signers.";
|
||||
JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId
|
||||
<< "]: " << "invalid batch signers.";
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -35,8 +35,8 @@ CancelOffer::preflight(PreflightContext const& ctx)
|
||||
|
||||
if (uTxFlags & tfUniversalMask)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: "
|
||||
<< "Invalid flags set.";
|
||||
JLOG(ctx.j.trace())
|
||||
<< "Malformed transaction: " << "Invalid flags set.";
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ CancelOffer::preclaim(PreclaimContext const& ctx)
|
||||
|
||||
if ((*sle)[sfSequence] <= offerSequence)
|
||||
{
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: "
|
||||
<< "Sequence " << offerSequence << " is invalid.";
|
||||
JLOG(ctx.j.trace()) << "Malformed transaction: " << "Sequence "
|
||||
<< offerSequence << " is invalid.";
|
||||
return temBAD_SEQUENCE;
|
||||
}
|
||||
|
||||
|
||||
@@ -130,6 +130,20 @@ Change::preclaim(PreclaimContext const& ctx)
|
||||
ctx.tx.isFieldPresent(sfReserveIncrementDrops))
|
||||
return temDISABLED;
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
!ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temMALFORMED;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfExtensionSizeLimit) ||
|
||||
ctx.tx.isFieldPresent(sfGasPrice))
|
||||
return temDISABLED;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
case ttAMENDMENT:
|
||||
case ttUNL_MODIFY:
|
||||
@@ -377,6 +391,12 @@ Change::applyFee()
|
||||
set(feeObject, ctx_.tx, sfReserveBase);
|
||||
set(feeObject, ctx_.tx, sfReserveIncrement);
|
||||
}
|
||||
if (view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
set(feeObject, ctx_.tx, sfExtensionComputeLimit);
|
||||
set(feeObject, ctx_.tx, sfExtensionSizeLimit);
|
||||
set(feeObject, ctx_.tx, sfGasPrice);
|
||||
}
|
||||
|
||||
view().update(feeObject);
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <xrpld/app/misc/CredentialHelpers.h>
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/misc/WasmHostFuncImpl.h>
|
||||
#include <xrpld/app/misc/WasmVM.h>
|
||||
#include <xrpld/app/tx/detail/Escrow.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/conditions/Condition.h>
|
||||
@@ -34,6 +36,8 @@
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
// During an EscrowFinish, the transaction must specify both
|
||||
// a condition and a fulfillment. We track whether that
|
||||
// fulfillment matches and validates the condition.
|
||||
@@ -118,9 +122,28 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
|
||||
if (tx.isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
// TODO: make this fee increase based on the extra compute run
|
||||
txnFees += 1000;
|
||||
}
|
||||
return txnFees;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||
!ctx.rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
@@ -157,14 +180,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||
!ctx.tx.isFieldPresent(sfCancelAfter))
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.rules.enabled(fix1571))
|
||||
{
|
||||
// In the absence of a FinishAfter, the escrow can be finished
|
||||
// immediately, which can be confusing. When creating an escrow,
|
||||
// we want to ensure that either a FinishAfter time is explicitly
|
||||
// specified or a completion condition is attached.
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
|
||||
!ctx.tx[~sfFinishFunction])
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
|
||||
"Condition, or FinishFunction.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const cb = ctx.tx[~sfCondition])
|
||||
@@ -189,6 +221,26 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
|
||||
if (code.size() == 0 ||
|
||||
code.size() > ctx.app.config().FEES.extension_size_limit)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "EscrowCreate.FinishFunction bad size " << code.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
HostFunctions mock;
|
||||
auto const re = preflightEscrowWasm(code, "finish", {}, &mock);
|
||||
if (!isTesSuccess(re))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
|
||||
return re;
|
||||
}
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
@@ -537,6 +589,8 @@ EscrowCreate::doApply()
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
|
||||
(*slep)[~sfData] = ctx_.tx[~sfData];
|
||||
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) && !isXRP(amount))
|
||||
{
|
||||
@@ -599,7 +653,12 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
// TODO: determine actual reserve based on FinishFunction size
|
||||
adjustOwnerCount(
|
||||
ctx_.view(),
|
||||
sle,
|
||||
ctx_.tx.isFieldPresent(sfFinishFunction) ? 2 : 1,
|
||||
ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -634,6 +693,13 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
!ctx.rules.enabled(featureCredentials))
|
||||
return temDISABLED;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
|
||||
!ctx.rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -643,7 +709,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
// If you specify a condition, then you must also specify
|
||||
// a fulfillment.
|
||||
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// Verify the transaction signature. If it doesn't work
|
||||
// then don't do any more work.
|
||||
@@ -672,6 +741,16 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
|
||||
{
|
||||
if (*allowance > ctx.app.config().FEES.extension_compute_limit)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ComputationAllowance too large: " << *allowance;
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx); !isTesSuccess(err))
|
||||
return err;
|
||||
|
||||
@@ -687,7 +766,10 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
|
||||
{
|
||||
extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP;
|
||||
}
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
@@ -766,25 +848,52 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow) ||
|
||||
ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// this check is done in doApply before this amendment is enabled
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
if (slep->isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "FinishFunction requires ComputationAllowance";
|
||||
return tefWASM_FIELD_NOT_INCLUDED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "FinishFunction not present, "
|
||||
"ComputationAllowance present";
|
||||
return tefNO_WASM;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(
|
||||
ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -1016,7 +1125,8 @@ EscrowFinish::doApply()
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) ||
|
||||
ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecNO_TARGET;
|
||||
@@ -1031,11 +1141,17 @@ EscrowFinish::doApply()
|
||||
|
||||
// Too soon: can't execute before the finish time
|
||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||
{
|
||||
JLOG(j_.debug()) << "Too soon";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Too late: can't execute after the cancel time
|
||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||
{
|
||||
JLOG(j_.debug()) << "Too late";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1043,13 +1159,35 @@ EscrowFinish::doApply()
|
||||
if ((*slep)[~sfFinishAfter] &&
|
||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||
(*slep)[sfFinishAfter])
|
||||
{
|
||||
JLOG(j_.debug()) << "Too soon?";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Too late?
|
||||
if ((*slep)[~sfCancelAfter] &&
|
||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||
(*slep)[sfCancelAfter])
|
||||
{
|
||||
JLOG(j_.debug()) << "Too late?";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
@@ -1099,17 +1237,59 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
if (!ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(ctx_, account_, destID, sled);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute custom release function
|
||||
if ((*slep)[~sfFinishFunction])
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "The escrow has a finish function, running WASM code...";
|
||||
// WASM execution
|
||||
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
std::string funcName("finish");
|
||||
|
||||
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
|
||||
|
||||
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
// already checked above, this check is just in case
|
||||
return tecINTERNAL;
|
||||
}
|
||||
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
|
||||
auto re =
|
||||
runEscrowWasm(wasm, funcName, {}, &ledgerDataProvider, allowance);
|
||||
JLOG(j_.trace()) << "Escrow WASM ran";
|
||||
if (re.has_value())
|
||||
{
|
||||
auto reValue = re.value().result;
|
||||
// TODO: better error handling for this conversion
|
||||
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
|
||||
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
|
||||
<< ", cost: " << re.value().cost;
|
||||
if (!reValue)
|
||||
{
|
||||
// ctx_.view().update(slep);
|
||||
return tecWASM_REJECTED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
|
||||
return re.error();
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
@@ -1184,7 +1364,11 @@ EscrowFinish::doApply()
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
adjustOwnerCount(
|
||||
ctx_.view(),
|
||||
sle,
|
||||
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
|
||||
ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
@@ -1395,7 +1579,11 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
adjustOwnerCount(
|
||||
ctx_.view(),
|
||||
sle,
|
||||
slep->isFieldPresent(sfFinishFunction) ? -2 : -1,
|
||||
ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
|
||||
@@ -36,6 +36,9 @@ public:
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -964,6 +964,22 @@ removeExpiredCredentials(
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
modifyWasmDataFields(
|
||||
ApplyView& view,
|
||||
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
|
||||
beast::Journal viewJ)
|
||||
{
|
||||
for (auto const& [index, data] : wasmObjects)
|
||||
{
|
||||
if (auto const sle = view.peek(keylet::escrow(index)))
|
||||
{
|
||||
sle->setFieldVL(sfData, data);
|
||||
view.update(sle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
removeDeletedTrustLines(
|
||||
ApplyView& view,
|
||||
@@ -1121,6 +1137,7 @@ Transactor::operator()()
|
||||
else if (
|
||||
(result == tecOVERSIZE) || (result == tecKILLED) ||
|
||||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
|
||||
(result == tecWASM_REJECTED) ||
|
||||
(isTecClaimHardFail(result, view().flags())))
|
||||
{
|
||||
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
||||
@@ -1133,13 +1150,16 @@ Transactor::operator()()
|
||||
std::vector<uint256> removedTrustLines;
|
||||
std::vector<uint256> expiredNFTokenOffers;
|
||||
std::vector<uint256> expiredCredentials;
|
||||
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
|
||||
|
||||
bool const doOffers =
|
||||
((result == tecOVERSIZE) || (result == tecKILLED));
|
||||
bool const doLines = (result == tecINCOMPLETE);
|
||||
bool const doNFTokenOffers = (result == tecEXPIRED);
|
||||
bool const doCredentials = (result == tecEXPIRED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials)
|
||||
bool const doWasmData = (result == tecWASM_REJECTED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
|
||||
doWasmData)
|
||||
{
|
||||
ctx_.visit([doOffers,
|
||||
&removedOffers,
|
||||
@@ -1148,7 +1168,9 @@ Transactor::operator()()
|
||||
doNFTokenOffers,
|
||||
&expiredNFTokenOffers,
|
||||
doCredentials,
|
||||
&expiredCredentials](
|
||||
&expiredCredentials,
|
||||
doWasmData,
|
||||
&modifiedWasmObjects](
|
||||
uint256 const& index,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
@@ -1183,6 +1205,13 @@ Transactor::operator()()
|
||||
(before->getType() == ltCREDENTIAL))
|
||||
expiredCredentials.push_back(index);
|
||||
}
|
||||
|
||||
if (doWasmData && before && after &&
|
||||
(before->getType() == ltESCROW))
|
||||
{
|
||||
modifiedWasmObjects.push_back(
|
||||
std::make_pair(index, after->getFieldVL(sfData)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1212,6 +1241,10 @@ Transactor::operator()()
|
||||
removeExpiredCredentials(
|
||||
view(), expiredCredentials, ctx_.app.journal("View"));
|
||||
|
||||
if (result == tecWASM_REJECTED)
|
||||
modifyWasmDataFields(
|
||||
view(), modifiedWasmObjects, ctx_.app.journal("View"));
|
||||
|
||||
applied = isTecClaim(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -1121,8 +1121,8 @@ Consensus<Adaptor>::checkLedger(std::unique_ptr<std::stringstream> const& clog)
|
||||
|
||||
auto netLgr =
|
||||
adaptor_.getPrevLedger(prevLedgerID_, previousLedger_, mode_.get());
|
||||
CLOG(clog) << "network ledgerid " << netLgr << ", "
|
||||
<< "previous ledger " << prevLedgerID_ << ". ";
|
||||
CLOG(clog) << "network ledgerid " << netLgr << ", " << "previous ledger "
|
||||
<< prevLedgerID_ << ". ";
|
||||
|
||||
if (netLgr != prevLedgerID_)
|
||||
{
|
||||
@@ -1213,8 +1213,7 @@ Consensus<Adaptor>::phaseOpen(std::unique_ptr<std::stringstream> const& clog)
|
||||
adaptor_.parms().ledgerIDLE_INTERVAL,
|
||||
2 * previousLedger_.closeTimeResolution());
|
||||
CLOG(clog) << "idle interval set to " << idleInterval.count()
|
||||
<< "ms based on "
|
||||
<< "ledgerIDLE_INTERVAL: "
|
||||
<< "ms based on " << "ledgerIDLE_INTERVAL: "
|
||||
<< adaptor_.parms().ledgerIDLE_INTERVAL.count()
|
||||
<< ", previous ledger close time resolution: "
|
||||
<< previousLedger_.closeTimeResolution().count() << "ms. ";
|
||||
@@ -1262,8 +1261,7 @@ Consensus<Adaptor>::shouldPause(
|
||||
<< "roundTime: " << result_->roundTime.read().count() << ", "
|
||||
<< "max consensus time: " << parms.ledgerMAX_CONSENSUS.count() << ", "
|
||||
<< "validators: " << totalValidators << ", "
|
||||
<< "laggards: " << laggards << ", "
|
||||
<< "offline: " << offline << ", "
|
||||
<< "laggards: " << laggards << ", " << "offline: " << offline << ", "
|
||||
<< "quorum: " << quorum << ")";
|
||||
|
||||
if (!ahead || !laggards || !totalValidators || !adaptor_.validator() ||
|
||||
@@ -1624,8 +1622,8 @@ Consensus<Adaptor>::updateOurPositions(
|
||||
if (!haveCloseTimeConsensus_)
|
||||
{
|
||||
JLOG(j_.debug())
|
||||
<< "No CT consensus:"
|
||||
<< " Proposers:" << currPeerPositions_.size()
|
||||
<< "No CT consensus:" << " Proposers:"
|
||||
<< currPeerPositions_.size()
|
||||
<< " Mode:" << to_string(mode_.get())
|
||||
<< " Thresh:" << threshConsensus
|
||||
<< " Pos:" << consensusCloseTime.time_since_epoch().count();
|
||||
|
||||
@@ -73,6 +73,15 @@ struct FeeSetup
|
||||
/** The per-owned item reserve requirement in drops. */
|
||||
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
|
||||
|
||||
/** The compute limit for Feature Extensions. */
|
||||
std::uint32_t extension_compute_limit{1'000'000};
|
||||
|
||||
/** The WASM size limit for Feature Extensions. */
|
||||
std::uint32_t extension_size_limit{100'000};
|
||||
|
||||
/** The price of 1 WASM gas, in micro-drops. */
|
||||
std::uint32_t gas_price{1'000'000};
|
||||
|
||||
/* (Remember to update the example cfg files when changing any of these
|
||||
* values.) */
|
||||
};
|
||||
|
||||
@@ -1140,6 +1140,12 @@ setup_FeeVote(Section const& section)
|
||||
setup.account_reserve = temp;
|
||||
if (set(temp, "owner_reserve", section))
|
||||
setup.owner_reserve = temp;
|
||||
if (set(temp, "extension_compute_limit", section))
|
||||
setup.extension_compute_limit = temp;
|
||||
if (set(temp, "extension_size_limit", section))
|
||||
setup.extension_size_limit = temp;
|
||||
if (set(temp, "gas_price", section))
|
||||
setup.gas_price = temp;
|
||||
}
|
||||
return setup;
|
||||
}
|
||||
|
||||
@@ -75,6 +75,12 @@ public:
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::uint32_t const& gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
@@ -93,6 +99,7 @@ public:
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -117,6 +117,7 @@ ApplyStateTable::apply(
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::optional<uint256 const> const& parentBatchId,
|
||||
std::optional<std::uint32_t> const& gasUsed,
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
@@ -127,11 +128,14 @@ ApplyStateTable::apply(
|
||||
std::optional<TxMeta> metadata;
|
||||
if (!to.open() || isDryRun)
|
||||
{
|
||||
TxMeta meta(tx.getTransactionID(), to.seq(), parentBatchId);
|
||||
TxMeta meta(tx.getTransactionID(), to.seq());
|
||||
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
|
||||
if (parentBatchId)
|
||||
meta.setParentBatchId(*parentBatchId);
|
||||
if (gasUsed)
|
||||
meta.setGasUsed(*gasUsed);
|
||||
Mods newMod;
|
||||
for (auto& item : items_)
|
||||
{
|
||||
|
||||
@@ -73,6 +73,7 @@ public:
|
||||
TER ter,
|
||||
std::optional<STAmount> const& deliver,
|
||||
std::optional<uint256 const> const& parentBatchId,
|
||||
std::optional<std::uint32_t> const& gasUsed,
|
||||
bool isDryRun,
|
||||
beast::Journal j);
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@ ApplyViewImpl::apply(
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
|
||||
return items_.apply(
|
||||
to, tx, ter, deliver_, parentBatchId, gasUsed_, isDryRun, j);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
|
||||
@@ -458,8 +458,7 @@ accountHolds(
|
||||
amount.clear(Issue{currency, issuer});
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "accountHolds:"
|
||||
<< " account=" << to_string(account)
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
|
||||
<< " amount=" << amount.getFullText();
|
||||
|
||||
return view.balanceHook(account, issuer, amount);
|
||||
@@ -640,8 +639,7 @@ xrpLiquid(
|
||||
STAmount const amount =
|
||||
(balance < reserve) ? STAmount{0} : balance - reserve;
|
||||
|
||||
JLOG(j.trace()) << "accountHolds:"
|
||||
<< " account=" << to_string(id)
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(id)
|
||||
<< " amount=" << amount.getFullText()
|
||||
<< " fullBalance=" << fullBalance.getFullText()
|
||||
<< " balance=" << balance.getFullText()
|
||||
|
||||
@@ -1272,8 +1272,8 @@ PeerImp::handleTransaction(
|
||||
{
|
||||
// If we've never been in synch, there's nothing we can do
|
||||
// with a transaction
|
||||
JLOG(p_journal_.debug()) << "Ignoring incoming transaction: "
|
||||
<< "Need network ledger";
|
||||
JLOG(p_journal_.debug())
|
||||
<< "Ignoring incoming transaction: " << "Need network ledger";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1132,9 +1132,9 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(m_journal.error()) << beast::leftw(18) << "Logic failed "
|
||||
<< "'" << source->name() << "' fetch, "
|
||||
<< results.error.message();
|
||||
JLOG(m_journal.error())
|
||||
<< beast::leftw(18) << "Logic failed " << "'" << source->name()
|
||||
<< "' fetch, " << results.error.message();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user